Close Submenu when opening another with ReactJS - javascript

Creating Submenus I find that when opening one of them all the others open and when closing it in the same way they all close.
This is my code:
const DashBoard = () => {
const [showDropdown, setShowDropdown] = useState(false)
return (
<>
<div>
<div>
<div>
<img
src={ Logo }
alt= "LOGO"
/>
</div>
<div>
<ul>
<li>
<div
onClick={ () => setShowDropdown(!showDropdown) }
>
<FiHome />
Submenu 1
</div>
{ showDropdown &&
(
<div>
<ul>
<li>Test</li>
<li >Test</li>
</ul>
</div>
)
}
</li>
</ul>
</div>
<div>
<ul>
<li className='group'>
<div
onClick={ () => setShowDropdown(!showDropdown) }
>
<FiHome />
Submenu 2
</div>
{ showDropdown &&
(
<div>
<ul>
<li>Test</li>
<li >Test</li>
</ul>
</div>
)
}
</li>
</ul>
</div>
</div>
<div>
<main>Main Content</main>
</div>
</div>
</>
)
}
How could I achieve that when opening a submenu the others are closed? I have reviewed that indicating an ID per submenu is possible but I don't know if it is the best way. Thanks.

setShowDropdown(!showDropdown)
This part is making all your dropdowns closed together. And your showDropdown state is only true/false value that cannot help you in identifying which dropdown is open/closed
I'd propose that you should assign menu item name to the state
const [showDropdown, setShowDropdown] = useState() //as default, no menu items are opening
According to your setup, I'd use your sub-menu name for the state value
setShowDropdown("Submenu 1")
onClick logic for opening/closing a sub-menu item
onClick={() => setShowDropdown(showDropdown === "Submenu 2" ? undefined : "Submenu 2")}
Add the condition to check current open/closed sub-menu items
showDropdown === "Submenu 1"
Full implementation can be
const DashBoard = () => {
const [showDropdown, setShowDropdown] = useState()
return (
<>
<div>
<div>
<div>
<img
src={ Logo }
alt= "LOGO"
/>
</div>
<div>
<ul>
<li>
<div
onClick={ () => setShowDropdown(showDropdown === "Submenu 1" ? undefined : "Submenu 1") }
>
<FiHome />
Submenu 1
</div>
{ showDropdown === "Submenu 1" &&
(
<div>
<ul>
<li>Test</li>
<li >Test</li>
</ul>
</div>
)
}
</li>
</ul>
</div>
<div>
<ul>
<li className='group'>
<div
onClick={ () => setShowDropdown(showDropdown === "Submenu 2" ? undefined : "Submenu 2") }
>
<FiHome />
Submenu 2
</div>
{ showDropdown === "Submenu 2" &&
(
<div>
<ul>
<li>Test</li>
<li >Test</li>
</ul>
</div>
)
}
</li>
</ul>
</div>
</div>
<div>
<main>Main Content</main>
</div>
</div>
</>
)
}

It would be good if you make it all dynamic using an array.
Here I have taken an array named menu which includes 3 properties title, visible, and child. And in the HTML, I have used the visible property to show the respective menu's child.
Demo: https://stackblitz.com/edit/react-3w2rns
Solution:
import React, { useState } from 'react';
import './style.css';
export default function App() {
const [menu, setMenu] = useState([
{
title: 'Menu 1',
visible: false,
child: [{ title: 'Test 1' }, { title: 'Test 1' }],
},
{
title: 'Menu 2',
visible: false,
child: [{ title: 'Test 2' }, { title: 'Test 2' }],
},
]);
const onMenuClick = (index) => {
menu[index].visible = !menu[index].visible;
setMenu([...menu]);
};
return (
<>
<div>
<div>
<div>
<img src="" alt="LOGO" />
</div>
{menu.map((menu, index) => (
<div>
<ul>
<li className="group">
<div onClick={() => onMenuClick(index)}>{menu.title}</div>
{menu.visible && (
<ul>
{menu.child.map((item) => (
<li>{item.title}</li>
))}
</ul>
)}
</li>
</ul>
</div>
))}
</div>
<div>
<main>Main Content</main>
</div>
</div>
</>
);
}

Related

How to assign a classname to particular division after map in React js?

I am mapping data inside a div, all i need a border to div of "lab_add_to_cart_samp1" classname after clicking that division. I am confused how to make a division show active onclick even after mapping the data, there is an array of allabtest from API call
<div className="lab_add_to_cart_samp">
{
allabtest.map((item, index) => {
return <div className="lab_add_to_cart_samp1" key={index}>
<div className="lab_add_to_cart_samp_img1">
<img src={REACT_APP_BASE_URL+"lab-test/download/"+item.id} />
</div>
<div className="lab_add_to_cart_test_desc">
<p id="labtest_desc_txt1">{item.name}</p>
<p id="labtest_desc_txt2">What it include :</p>
<div className="labtest_desc1">
<div className="labtest_desc_detail">
<ul>
<li>Platate</li>
<li>CBC</li>
<li>HB</li>
</ul>
</div>
<div>
<ul id="lab_test_detail">
<li>PCV</li>
<li>WBC/TLC</li>
<li>WBC/TLC</li>
</ul>
</div>
<div>
<ul id="lab_test_detail">
<li>DLC</li>
<li>RBC</li>
<li>ESR</li>
</ul>
</div>
<div>
<ul id="lab_test_detail">
<li>Platelets</li>
<li>Reticilocytes</li>
<li>Blood Grouping Rh type</li>
</ul>
</div>
</div>
</div>
<div className="lab_add_to_cart_price">
<p>Rs. {item.price}</p>
<div className="lab_add_to_cart_atc">
<button onClick={()=>addtocart(item)}>
<p>Add to Cart</p>
</button>
</div>
</div>
</div>
})
}
</div>
If you want to set active for multiple divs at the same time you can do it like this
const allabtest = [
{id: 'one', name: 'one'},
{id: 'two', name: 'two'},
{id: 'three', name: 'three'},
]
function App() {
const [activeCart, setActiveCart] = useState({});
return (
<div className="lab_add_to_cart_samp">
{allabtest.map((item, index) => {
return (
<div
className={
activeCart[item.id] === true
? "lab_add_to_cart_samp1 active"
: "lab_add_to_cart_samp1"
}
onClick={() =>
setActiveCart({
...activeCart,
[item.id]: !Boolean(activeCart[item.id])
})
}
key={item.id}
>
<div>{item.name}</div>
</div>
);
})}
</div>
);
}
If you want to track only one div then
function App() {
const [activeCart, setActiveCart] = useState();
return (
<div className="lab_add_to_cart_samp">
{allabtest.map((item, index) => {
return (
<div
className={
activeCart === item.id
? "lab_add_to_cart_samp1 active"
: "lab_add_to_cart_samp1"
}
onClick={() => setActiveCart(item.id)}
key={item.id}
>
{/* Rest of your stuffs */}
</div>
);
})}
</div>
);
}

How to optimize toggling className between different menu items by React?

I have header component, where I want to toggle className between all the elements of menu (if one of the elements of menu is active and user is clicking to another element - this element become active and all others no). I have a code like this
import React, { useState } from 'react';
import './header.scss';
export const Header = ({ favoriteCount }) => {
const [activeIndex, setActiveIndex] = useState(0);
function toggleClass(index) {
setActiveIndex(index);
}
return (
<header className="header">
<div className="container header-container">
<ul className="header-menu">
<li>
<a
className={
activeIndex === 0
? 'header-menu__link active'
: 'header-menu__link'
}
onClick={() => {
toggleClass(0);
}}
href="##"
>
Characters
</a>
</li>
<li>
<a
className={
activeIndex === 1
? 'header-menu__link active'
: 'header-menu__link'
}
onClick={() => {
toggleClass(0);
}}
href="##"
>
Favorites
</a>
</li>
</ul>
<div className="header-favorite-count">
<i className="far fa-heart"></i>
{favoriteCount}
</div>
</div>
</header>
);
};
and styles to visualise toggling classes
&-menu__link {
color: lightgray;
}
.active {
color: #fff;
}
This approach is working but looks creepy. Maybe somebody knows how to optimize it?
I wouldn't use the index, I'd use the text of the item. I'd also include that text in the href so that there's an indication of what the anchor leads to. To avoid repeated code, you might put the menu items in a reusable array, something like this:
const menuItems = [
"Characters",
"Favorites",
];
export const Header = ({ favoriteCount }) => {
const [activeItem, setActiveItem] = useState("");
const setActiveItem = useCallback((event) => {
setActiveItem(event.currentTarget.href.substring(2));
}, []);
const list = menuItems.map(item =>
<li key={item}>
<a
className={`header-menu__link ${item === activeItem ? "active" : ""}`}
onClick={setActiveItem}
href={"##" + item}>
{item}
</a>
</li>
);
return (
<header className="header">
<div className="container header-container">
<ul className="header-menu">
{list}}
</ul>
<div className="header-favorite-count">
<i className="far fa-heart"></i>
{favoriteCount}
</div>
</div>
</header>
);
};

show and hide associated sub menu when particular menu is clicked

I am designing a responsive mega navigation bar using reactjs. It's working for desktop device but on mobile view, I could not able to show or hide sub menu when its associated parent menu is clicked. This is what I am trying to do
const menus = [
{ id: 1, name: "Home", link: "/" },
{
id: 3,
name: "New",
children: [{ id: 1, name: "New Arrival", link: "/new" }]
},
{
id: 2,
name: "Shop",
children: [
{
id: 1,
name: "Men's Fashion",
children: [
{
id: 1,
name: "Polo",
link: "/polo"
},
{ id: 2, name: "Tees", link: "/tees" }
]
},
{
id: 2,
name: "Women Fashion",
children: [
{
id: 1,
name: "Shorts",
link: "/shorts"
},
{
id: 2,
name: "Tees",
link: "/women/tees"
}
]
}
]
}
];
const MegaMenu = () => {
const [active, setActive] = React.useState(false);
const [toggle, setToggle] = React.useState(false);
const [activeMenuName, setActiveMenuName] = React.useState("");
const handleMainMenuClick = () => {
setActive(!active);
};
const toggleDrawer = () => {
setToggle(!toggle);
};
const hideSubMenu = () => {
setActive(!active);
};
const handleMenuName = (menuName) => {
setActiveMenuName(menuName);
};
return (
<>
<header className="header">
<div className="container">
<div className="row v-center">
<div className="header-item item-left">
<div className="logo">
Brand
</div>
</div>
{/* menu start here */}
<div className="header-item item-center">
<div
className={`menu-overlay ${toggle ? "active" : ""}`}
onClick={toggleDrawer}
></div>
<nav className={`menu ${toggle ? "active" : ""}`}>
<div className={`mobile-menu-head ${toggle ? "active" : ""}`}>
<div className="go-back" onClick={hideSubMenu}>
Back
</div>
<div className="current-menu-title">{activeMenuName}</div>
<div className="mobile-menu-close" onClick={toggleDrawer}>
×
</div>
</div>
<ul className="menu-main" onClick={handleMainMenuClick}>
{menus.map((menu) => {
return (
<li
key={menu.id}
className={
menu.children ? "menu-item-has-children" : ""
}
onClick={() => handleMenuName(menu.name)}
>
{menu.link ? (
<a href={menu.link}>
{menu.name} <i class="fa fa-angle-down"></i>
</a>
) : (
{menu.name}
)}
{menu.children && (
<div
className={`sub-menu mega-menu mega-menu-column-4 ${
active ? "active" : ""
}`}
>
{menu.children.map((child) => {
return (
<div
className="list-item text-center"
key={child.id}
>
<a href="#">
<img src="img/p1.jpg" alt="new Product" />
<h4 className="title">{child.name}</h4>
</a>
</div>
);
})}
</div>
)}
</li>
);
})}
</ul>
</nav>
</div>
<div className="header-item item-right">
<a href="#">
<i className="fas fa-search"></i>
</a>
<a href="#">
<i className="far fa-heart"></i>
</a>
<a href="#">
<i className="fas fa-shopping-cart"></i>
</a>
{/* mobile menu trigger */}
<div className="mobile-menu-trigger" onClick={toggleDrawer}>
<span></span>
</div>
</div>
</div>
</div>
</header>
</>
);
};
export default MegaMenu;
I have a code on sandbox as well and here it is
https://codesandbox.io/s/weathered-resonance-9n2hr?file=/src/mega-menu/index.js:50-4734
You should set active only to the activeMenuName that is clicked. Also when click go back you should reset activeMenuName to the default state. Also, I check if toggle is enabled so only onClick events are enabled and vice versa.
Check my code it's tested.
const MegaMenu = () => {
const [active, setActive] = React.useState(false);
const [toggle, setToggle] = React.useState(false);
const [activeMenuName, setActiveMenuName] = React.useState("");
const handleMainMenuClick = () => {
setActive(!active);
};
const toggleDrawer = () => {
setActiveMenuName("");
setToggle(!toggle);
};
const hideSubMenu = () => {
setActiveMenuName("");
};
const handleMenuName = (menuName) => {
setActiveMenuName(menuName);
};
const renderLiComp = (menu) =>{
if(toggle){
return <li
key={menu.id}
className={ menu.children ? "menu-item-has-children" : "" }
onClick={() =>handleMenuName(menu.name)}
>
{menu.link
? (</i>)
: ({menu.name})}
{activeMenuName && getSelectedChildren()}
</li>;
}else{
return <li
key={menu.id}
className={ menu.children ? "menu-item-has-children" : "" }
onMouseEnter={()=>handleMenuName(menu.name)}
>
{menu.link
? (</i>)
: ({menu.name})}
{activeMenuName && getSelectedChildren()}
</li>;
}
}
const renderListElements = () =>{
return menus.map(renderLiComp);
}
const renderChildren = (childs) =>{
const children = childs.map(x =>
<div className="list-item text-center" key={x.id}>
<a href="#"><img src="img/p1.jpg" alt="new Product" />
<h4 className="title">{x.name}</h4>
</a>
</div>
);
return children;
}
const getSelectedChildren = () =>{
return menus.map(x => {
let selected;
if(x.name === activeMenuName){
if(x.children){
selected = <div key={x.id} className='sub-menu mega-menu mega-menu-column-4 active'>{renderChildren(x.children)}</div>;
}else{
selected = <div key={x.id} className='sub-menu mega-menu mega-menu-column-4 active'></div>;
}
}
return selected;
});
}
return (
<>
<header className="header">
<div className="container">
<div className="row v-center">
<div className="header-item item-left">
<div className="logo">
Brand
</div>
</div>
{/* menu start here */}
<div className="header-item item-center">
<div
className={`menu-overlay ${toggle ? "active" : ""}`}
onClick={toggleDrawer}
></div>
<nav className={`menu ${toggle ? "active" : ""}`}>
<div className={`mobile-menu-head ${toggle ? "active" : ""}`}>
<div className="go-back" onClick={hideSubMenu}>
Back
</div>
<div className="current-menu-title">{activeMenuName}</div>
<div className="mobile-menu-close" onClick={toggleDrawer}>
×
</div>
</div>
<ul className="menu-main" onClick={handleMainMenuClick}>
{renderListElements()}
</ul>
</nav>
</div>
The sandbox Link https://codesandbox.io/s/nifty-cloud-kuryf?file=/src/mega-menu/index.js

Hide other previous div/dropdown React

I am wanting to hide other sibling divs (dropdowns in my case) when I click the statusPillDropdown
so far I click I am setting the status to true and opening the div,
{DropDown ${toggleStatusDropdown ? "open": ""}}
Do I just need to set the state to false for previously opened ones? Not sure how to do this.
thank you
import React, { useState } from "react";
import "./StatusPillDropdown.scss";
function StatusPillDropdown({
cellData,
rowItemId,
onStatusPillDropdownChange
}) {
const [toggleStatusDropdown, setToggleStatusDropdown] = useState();
const toggleDropdown = (action, rowItemId, e) => {
if (action === "pillClick") {
setToggleStatusDropdown(true);
} else {
onStatusPillDropdownChange(rowItemId, e.target.getAttribute("value"));
setToggleStatusDropdown(false);
}
};
const renderstatusPillDropdown = (cellData, rowItemId) => (
<React.Fragment>
<span
className="statusPillDropdown"
onClick={() => toggleDropdown("pillClick", rowItemId)}
>
<span className={`status-pill ${cellData.type}`}>{cellData.text}</span>
</span>
<div className="status">
<div className="dropdown-container">
<div className={`DropDown ${toggleStatusDropdown ? "open" : ""}`}>
<ul>
<li
value="Information only"
onClick={e => toggleDropdown("liClick", rowItemId, e)}
>
<span></span>Information only
</li>
<li
value="Unresolved"
onClick={e => toggleDropdown("liClick", rowItemId, e)}
>
<span className="unresolved"></span>Unresolved
</li>
<li
value="Partially Resolved"
onClick={e => toggleDropdown("liClick", rowItemId, e)}
>
<span className="partyResolved"></span>Partially Resolved
</li>
<li
value="Fully Resolved"
onClick={e => toggleDropdown("liClick", rowItemId, e)}
>
<span className="resolved"></span>Fully Resolved
</li>
</ul>
</div>
</div>
</div>
</React.Fragment>
);
return (
<React.Fragment>
{renderstatusPillDropdown(cellData, rowItemId)}
</React.Fragment>
);
}
export default StatusPillDropdown;

React js - pass value to child to parent to another child

I am trying to pass the data from Child > parent > child
Child
{this.state.data.map((item, index) => (
<li className='card' key={index}>
<span>{item.continent} </span>
<ul className="accordion-body">
{item.regions.map((c, i) => (
<li key={i} onClick={this.props.toggleContent}>
<img src={c.flag}/> {c.country}
</li>
))}
</ul>
</li>
))}
Basically I need to get selected country and some other values from the child and pass to parent
and pass those values to another child.
My Parent
<div className="modal-header">
<h2>Choose your {title}</h2>
<a href="#" className="model-close" data-dismiss="modal" aria-label="Close"><i
className="fa fa-times-circle"></i></a>
</div>
<div className="modal-body">
{showCountry && <CountryList toggleContent={this.toggleContent}/>}
{showLanguages && <RegionList country={country} flag={flag} languages={languages}
toggleContent={this.toggleContentRegion.bind(this)}/>}
</div>
and
toggleContent = () => {
this.setState({
...this.state,
showCountry: !this.state.showCountry,
showLanguages: !this.state.showLanguages,
title: 'language',
country: 'country',
languages: [],
flag: 'flag'
});
}
I tried to use below
<li key={i} onClick={this.props.toggleContent(c.country)}>
<img src={c.flag}/> {c.country}
</li>
and access it from parent
toggleContent = (country) => {
this.setState({
...this.state,
showCountry: !this.state.showCountry,
showLanguages: !this.state.showLanguages,
title: 'language',
country: country,
languages: [],
flag: 'flag'
});
}
But, my components not working correctly When do that and always shows the 2 child component.
Are there any proper way to pass the data to parent from a json array?
So the best way I would handle this would be to make the import your parent class components into the child , place it at the very top of the child JSX but hide it by default. The modal would be fixed, background covering the full page and at a z-index higher than the rest of the child components, so that way only the modal contents are the only accessible things . You would have a state that "toggles on" the modal for each click of the item list and a close button that toggles it off. You would update the modal content and toggle it on for every click
In terms of the second child, you can just show it on the same modal
Found a way to do this :)
render() {
var toggleContent = this.props.toggleContent;
return (
<div className="modal-wrapper">
<ul className="country-list">
{this.state.data.map((item, index) => (
<li className='card' key={index}>
<span>{item.continent} </span>
<ul className="accordion-body">
{item.regions.map((c, i) => (
**<li key={i} onClick={() => toggleContent(c.country,c.flag, c.languages, c.region)} >**
<img src={c.flag}/> {c.country}
</li>
))}
</ul>
</li>
))}
</ul>
</div>
);
}
Changed below line
onClick={() => toggleContent(c.country,c.flag, c.languages, c.region)

Categories

Resources