Map function is not working properly when I hit onClick - javascript

So I am making this project in ReactJs, which has a sidebar, where I am trying to implement dropdown menu.
Required Behavior
If I click in any of the option of the sidebar, if it has a submenu, it will show. And close upon again clicking.
Current Behavior
If I click any of the options, all the submenus are showing at once.
For example if I click publications option, it shows me all the options, such as featured publications, journal publications.
How do I fix that?
My sidebarItems array
const sidebarItems = [
{
title: "Publications",
url: "#",
subMenu: [
{
title: "Journal Publications",
url: "#",
},
{
title: "Featured Publications",
url: "#",
},
],
},
{
title: "Team Members",
url: "#",
subMenu: [
{
title: "Current Members",
url: "#",
},
{
title: "Lab Alumni",
url: "#",
},
],
},
{
title: "Projects",
url: "#",
subMenu: [
{
title: "Project 1",
url: "#",
},
{
title: "Project 2",
url: "#",
},
{
title: "Project 3",
url: "#",
},
],
},
{
title: "News",
url: "#",
},
{
title: "Contact Us",
url: "#",
},
];
export default sidebarItems;
The Sidebar Component
import { useState } from "react";
import { Box, Text } from "#chakra-ui/react";
import sidebarItems from "./sidebarItems";
export default function Sidebar() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Box>
{sidebarItems.map((items) => {
return (
<Box
width='200px'
height='40px'
textAlign='center'
cursor='pointer'
onClick={() => {
setIsOpen(!isOpen);
}}
>
<Text>{items.title}</Text>
{isOpen
? items.subMenu?.map((item) => {
return <Text>{item.title}</Text>;
})
: ""}
</Box>
);
})}
</Box>
</div>
);
}

You have to use an array state variable. Your single state variable isOpen is dictating all the subMenus here:
{isOpen
? items.subMenu?.map((item) => {
return <Text>{item.title}</Text>;
})
: ""}
You need to have an array state variable here, so each sidebar item has a corresponding boolean to dictate opening/closing of value.
const [isOpen, setIsOpen] = useState(Array(sidebarItems.length).fill(false));
Now you have to ensure that you are setting it correctly and manipulating the right array element.
onClick={() => {
let newIsOpen = [...isOpen];
newIsOpen[index] = !isOpen[index];
setIsOpen(newIsOpen);
}}
I hope this helps you reach your solution

This is happening because you are using wrong logic and you don't specify which submenu should be shown.
First, delete the current state and dont use it.
Then, you should define another state like:
const [selectedMenu, setSelectedMenu] = useState("");
then, define this function:
const handleClick = (title) => {
setSelectedMenu(title);
}
after that, once you click on Box, you should invoke function like this:
onClick={() => handleClick(item.title)}
consequently, you should write your logic like this:
<Text>{items.title}</Text>
{item.title === selectedMenu
? items.subMenu?.map((item) => {
return <Text>{item.title}</Text>;
})
: ""}

I think the problem is occurring because you have only 1 state variable set for every sidebar option. Every sidebar option should have its own variable keeping track of whether its submenu should open or not.
In your code, when the isOpen state variable is set to true then when the function maps over all the options the variable's value will always be true.
Try setting a variable for each of the menu options which contains whether the submenu should open or not.

Related

Mapping an array in my Next JS app not working

In my Next JS app I'm getting array and mapping them for the nav bar. but when i do it throws an error of TypeError: _utils_navigation__WEBPACK_IMPORTED_MODULE_6___default(...).map is not a function. my code is as follows.
{NavRoutes.map((navs, index) => {
console.log(navs.title);
console.log('bhsjvhjvs');
return (
<Link href={navs.link}>
<div className={`${
router.asPath === navs.link
? "text-active-red"
: ""
}`}>{navs.title}</div>
</Link>
)
})}
and the array
const NavRoutes = [
{
title: "HOME",
link: "/"
},
{
title: "NEWS",
link: "/news"
},
{
title: "CHANNEL",
link: "/channels"
},
{
title:"SHOWS",
link: "/shows"
},
{
title: "SCHEDULE",
link: "/schedules"
},
{
title: "CONTACT",
link: "/contact"
},
]
export default NavRoutes;
so how can i work through this?
check how you have imported it.
It should be like this
import NavRoutes from "../utils/navigations.js";
And apart from this, try to console NavRoutes and check you are getting expected value or not.

How to manage state for navbar with multiple dropdown items?

i have navbar with 2 dropdowns that are managed with state. My issue is that when i click on one because both dropdowns open and close at the same time.
How can i make sure only the dropdown clicked on is open ?
const [show, setShow] = useState(false);
const [show1, setShow1] = useState(false);
menuItems.map((item) => {
console.log(item.href, item.submenuItems)
return (
item.submenu === true ?
<li key= {item.id }className="nav-item dropdown"
ref={ref}>
<Link
key={item.id}
href={item.href}
passHref
spy={true}
smooth={true}
offset={-70}
duration={500}
//className="nav-item"
>
<a
onClick={() => {
setShow(!show)
}}
className="nav-link rounded dropdown-toggle
dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded={show ? "true" : "false"}
id="navbarDropdown"
role="button"
>
{item.title}
</a>
</Link>
As mentioned earlier, You would need to create a separate file to track the individual mapped item as currently, you are mapping all the items to the same useState hook. So, it will toggle for all since its being used by each item. As you can see from the above example code you provided inside the <a> tag
onClick={() => setShow(!show)}
Just create a new file such as MenuItem.js
MenuItem.js
In this file you would need to add over the HTML code you are generating for the mapped data and also add the useState Hook to this file
import { useState } from "react";
export default HeaderItem = ({ item }) => {
const [show, setShow] = useState(false);
return (
<li key={item.id} className="nav-item dropdown">
<a
onClick={() => {
setShow(!show);
}}
className="nav-link rounded dropdown-toggle
dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded={show ? "true" : "false"}
id="navbarDropdown"
role="button"
>
{item.title} - {`${show}`}
</a>
{show && item.submenu
? item.submenuItems.map((subitem) => (
<ul>
<li>{subitem.id}</li>
</ul>
))
: ""}
</li>
);
};
File where you are mapping the data probably
After that you would need to make your changes in your current file where you are mapping the data. I took the liberty to copy the data off of the sandbox link you shared and used it to go over items and when clicked it should display the inside submenu contents for that item.
// import "./styles.css";
import HeaderItem from "./HeaderItem";
export default function App() {
const menuItems = [
{
id: "home",
title: "home",
href: "/",
submenu: false,
submenuItems: []
},
{
id: "services",
title: "services",
href: "/#services",
submenu: true,
submenuItems: [
{
id: "conciergerie",
title: "conciergerie",
href: "/ourServices/conciergerie"
},
{
id: "property management",
title: "property management",
href: "/ourServices/conciergerie"
}
]
},
{
id: "properties",
title: "properties",
href: "/#properties",
submenu: true,
submenuItems: [
{
id: "buy",
title: "buy",
href: "https://www.idealista.com"
},
{
id: "rent",
title: "rent",
href: "https://www.idealista.com"
}
]
},
{
id: "about-us",
title: "about us",
href: "/#about-us",
submenu: false,
submenuItems: []
},
{
id: "team",
title: "team",
href: "/#team",
submenu: false,
submenuItems: []
},
{
id: "contact",
title: "contact",
href: "/#contact",
submenu: false,
submenuItems: []
}
];
const headerItems = menuItems.map((item) => {
return item.submenu === true ? <HeaderItem item={item} /> : null;
});
return <div className="App">{headerItems}</div>;
}
Add your state inside your map so that map has its own state. Make sure you delete it from the main component ;)
menuItems.map((item) => {
console.log(item.href, item.submenuItems)
const [show, setShow] = useState(false);
return (
...
You don't need to (and should not) set a state inside any loop.
You could just give one state to one dropdown and one state to the other.
const [dropdownOpen1, setdropdownOpen1] = useState(false)
const [dropdownOpen2, setdropdownOpen2] = useState(false)
As mentioned above, you create two separate dropdown components, with each dropdown having its own state per component.
This way each dropdown has its own state, and you can't reuse that state for another dropdown.

React .map function not passing data to useState

Why is my setSelected useState not accepting the data from the .map function? I have the following react js code:
const [ selected, setSelected ] = useState(null)
const sectionItems = [
{ id: "1", title: "title1", description: "description1" },
{ id: "2", title: "title2", description: "description2" },
{ id: "3", title: "title3", description: "description3" },
]
I am mapping through the sectionItems and rendering a modal, based on if selected has an item or not:
{sectionItems.map((section, index) => {
return (
<div key={section.id} className="processSection1" onClick={setSelected(section) >
<div className="processTitle" >{section.title}</div>
</div>
)
})}
{selected ? <Modal title={selected.title} description={selected.description} /> : " "}
Problem: Why cant I pass the data into setSelected? Or the more precise question is, how can I render the modal with each sectionItem?
Also am getting this error: Too many re-renders. React limits the number of renders to
prevent an infinite loop.
you have to use onClick like this
onClick={()=>setSelected(section)}
If you want to add a value to a function you should use an inline function inside the onClick. Right now you are triggering the function for each rendering at render time.
Change:
onClick={setSelected(section)}
to:
onClick={() => setSelected(section)}

Changing state of object nested in array of objects

As stated in the title I'm trying to change the state of an object nested in an array of objects. I just can not get this to work. I've added a sample of what I'm trying to do in a codesandbox.
I'm using a Material-UI component call ToggleButton (link to demo ToggleButton). I want to change the state to toggle the buttons in the group. I was able to get this working for a create function but can not get it working for my update function.
Trying to change the values of the object in the array is just not working for me. Below are some things I've tried to no success. I want to change the IsTrue: so I can toggle the button to display the users selection.
setAppetizer(true);
setRecipeObjectState({
...recipeObjectState,
Category: [
...recipeObjectState.Category,
{
IsTrue: appetizer,
category: "Appetizer",
},
],
});
This just adds more buttons to my button group.
setAppetizer(true);
setRecipeObjectState((prevState) => ({
...prevState,
Category: [
{
IsTrue: appetizer,
category: "Appetizer",
},
],
}));
I'm just lost now at this point. I just want to also state that this is my first React project that is not just a sandbox for learning the framework and I'm also a jr. developer trying to learn. I have search stackoverflow and nothing has helped me out. I hope I have included enough information for someone to help out.
Your state and app appear to be very convoluted, but the general idea when updating nested array state is to shallowly copy the state at each level where an update is being made. Use array::map to map the Category property to a new array object reference and when the category matches toggle the IsTrue "selected" property.
setRecipeObjectState((prevState) => ({
...prevState,
Category: prevState.Category.map((category) =>
category.category === newCategory // <-- newCategory value from toggled button
? {
...category,
IsTrue: !category.IsTrue
}
: category
)
}));
Since your "selected" calculation is selected={Boolean(item.IsTrue)} you'll want to ensure your IsTrue element values are actually togglable, i.e. just store the boolean value right in the array.
const recipeObject = {
AuthorId: authorId,
BookAuthor: bookAuthor,
BookTitle: bookTitle,
Calories: parseInt(calories),
Category: [
{
IsTrue: false,
category: "Appetizer"
},
{
IsTrue: false,
category: "Breakfast"
},
{
IsTrue: false,
category: "Soup / Salad"
},
{
IsTrue: false,
category: "Vegetarian"
},
{
IsTrue: true,
category: "Meat (Beef, Pork, Chicken)"
},
{
IsTrue: false,
category: "Fish"
},
{
IsTrue: false,
category: "Dessert"
}
],
Description: description,
DurationInMinCook: parseInt(durationInMinCook),
DurationInMinPrep: parseInt(durationInMinPrep),
ImageUrl: imageUrl,
Ingredients: addedIngredients, // array
Instructions: addedInstructions, // array
IsRecipe: true,
Likes: 0,
RecipeId: selectedRecipeId,
ServingSize: parseInt(servingSize),
Title: title,
YouTubeUrl: youTubeUrl
};
You mutating the same reference, you need to render a copy or the component won't render (shallow comparison):
Try updating state like this.
const oldState = recipeObjectState;
oldState.Category = [
{
IsTrue: appetizer,
category: "Appetizer"
}
];
setRecipeObjectState(oldState);
I didn't try your component because it's huge.
Updating a single object property in an array of objects seems to be a very common use-case. Here is generally how you do that. Suppose id is a unique identifier of each object and that we want to toggle selected:
import React, { useState } from "react";
import "./styles.css";
import faker from "faker";
const array = [];
for (let id = 1; id < 10; id++) {
array.push({
id,
name: faker.name.firstName(),
age: faker.random.number(100),
selected: false
});
}
export default function App() {
const [list, setList] = useState(array);
const onClick = (id) => (event) => {
setList((list) =>
list.map((item) =>
item.id === id ? { ...item, selected: !item.selected } : item
)
);
};
return (
<div className="App">
Click on a item:
<ul>
{list.map(({ id, name, age, selected }) => (
<li key={id} onClick={onClick(id)} className="item">
{name} {age}
{selected ? " ✅" : null}
</li>
))}
</ul>
</div>
);
}
https://codesandbox.io/s/xenodochial-river-9sq6g?file=/src/App.js:0-825

My state open all of my submenu but i just want one submenu open

I have a left-menu and when you click on a element, the sub-menu of the element appear.
But with my actual code, when a click on a element, all of my submenu appear.
I know my method is not right, but i don't know how to do :(
My example code :
import { useState } from 'react'
export default function Menu(){
const [visibleSubCategorie, setVisibleSubCategorie] = useState(false)
const Menu = [{
name: 'Homme', link : '/homme-fr', subCategory: false
}, {
name: 'Femme', link : '/femme-fr', subCategory: [{
name: 'haut', link : '/femme-haut-fr'
},{
name: 'bas', link : '/femme-bas-fr'
}]
},{
name: 'Enfant', link : '/enfant-fr', subCategory: [{
name: 'haut', link : '/enfant-haut-fr'
},{
name: 'bas', link : '/enfant-bas-fr'
}]
}]
console.log("Menu -> Menu", Menu)
return(
<>
{Menu.map(item=>
<div>
{item.subCategory ?
<>
<button type="button" onClick={() => setVisibleSubCategorie(!visibleSubCategorie)}>{item.name}</button>
{visibleSubCategorie && item.subCategory.map(subCategorys=>
<>
<p>{subCategorys.name}</p>
</>
)}
</>
:
<a href={item.link}>{item.name}</a>
}
</div>
)}
</>
)
}``
example : when i click at the button "Femme" to see the sub-category of femme, it's like i click too on the button "Enfant".
I can create a composant and make the usestate "const [visibleSubCategorie, setVisibleSubCategorie] = useState(false)" inside and this composant inside the map but i know another method exist.
You are using the same piece of state to control all of your subCategories. A possible solution would be to useState as an array of string values for each subcategory.
const [visibleSubCategorie, setVisibleSubCategorie] = useState([])
setVisibleSubCategorie([...visibleSubCategorie, subCategorys.name])
Then check to see if that name exists in the array to know if you should show the subcategory.
{visibleSubCategorie.includes(subCategorys.name) && item.subCategory.map(subCategorys=>
You will then have to remove that item from the array when closing.
You could solve this using a method similar to what #Kyler suggested.
I suggest using a HOC, like this:
const setOpen = (setOpen, opened) => () => setOpen(!opened);
And then in your JSX:
onClick={setOpen(setVisibleSubCategorie, visibleSubCategorie)}
Note that in order for this to work, you'd have to have state for each of your sections.

Categories

Resources