React .map function not passing data to useState - javascript

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)}

Related

Map function is not working properly when I hit onClick

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.

How to input array to another array reactjs

Hi I'm a beginner in reactjs, I'm trying map array and insert the file into another array, and after insert, I map the file into the table, but I got Error "Maximum update depth exceeded"
This is my code
import React, { useEffect, useState } from "react";
import "../Components.css";
import { MDBDataTable } from "mdbreact";
import AuthService from "../../Services/AuthService";
// import AuthService from "../Services/AuthService";
export default function Dataadmin() {
const [Searchfile, setSearchfile] = useState([]);
const [data, setData] = useState({
columns: [
{
label: "No",
field: "no",
sort: "asc",
},
{
label: "Title",
field: "title",
sort: "asc",
},
{
label: "Singer",
field: "singer",
sort: "asc",
},
{
label: "Genre",
field: "genre",
sort: "asc",
},
{
label: "Country",
field: "country",
sort: "asc",
},
{
label: "Action",
field: "action",
sort: "asc",
},
],
rows: [],
});
AuthService.getalldata().then((res) => {
setSearchfile(res.data);
});
useEffect(() => {
Searchfile.map((item, index) => {
const cloned = { ...data };
cloned.rows.push({
no: index + 1,
title: item.title,
singer: item.singer,
genre: item.genre,
country: item.country,
action: (
<>
<button className="btn-action">
<i className="fas fa-pencil-alt"></i>
</button>
<button className="btn-action" style={{ marginLeft: "1vh" }}>
<i className="far fa-trash-alt"></i>
</button>
</>
),
});
setData(cloned);
});
}, [Searchfile, data]);
return (
<div className="div-admin">
<div className="table-adminss">
<MDBDataTable
className="mytable-admin"
striped
bordered
small
data={data}
/>
</div>
</div>
);
}
Can someone explain to me why I get an error and how to fix it? , hope you guys understand what I'm asking :D
There are two scenarios in react in which a component re-renders
When the state changes (In this case, an example is searchFile,)
When the props changes ( The properties passed to the component)
The function
AuthService.getalldata().then((res) => {
setSearchfile(res.data);
});
is called each time the component renders calling the setSearchfile. So once the setSearchfile is called the component re-renders once again calling the same function mentioned above(Authservice.getAllData()).This process repeats. So this will result in an infinite loop which the browser cannot handle. Hence you get the above error.
Moving the (Authservice.getAllData()) into the method of useEffect should solve the maximum update depth exceeded.
It causes because of you set setData inside a loop.
One you can do, you can store the array inside a const and setData(...data, *that const*) outside of the loop. you don't need loop here actually. As far I know MDDataTable map data itself.
according to your code you can simply do it,
useEffect(() => {
setData(...data, Searchfile) //if your Searchfile contains data
});
}, [Searchfile, data]);
Hope you got this.

Vue reactivity, prevent re-rendering component on prop update with input v-model

I am trying to workout how I can mutate a prop in a text input without causing the component to re-render.
Here is my code
//App.vue
<template>
<div class="row">
<category-component v-for="category in categories" :key="category.title" :category="category" />
</div>
</template>
export default {
name: "App",
data() {
return {
categories: [
{
title: "Cat 1",
description: "description"
},
{
title: "Cat 2",
description: "description"
},
{
title: "Cat 3",
description: "description"
}
]
};
},
components: {
CategoryComponent
}
}
// CategoryComponent.vue
<template>
<div>
<input type="text" v-model="category.title">
<br>
<textarea v-model="category.description"></textarea>
</div>
</template>
When I update the text input, the component re-renders and I lose focus. However, when I update the textarea, it does not re-render.
Can anyone shed any light on this? I am obviously missing something simple with vue reactivity.
Here is a basic codesandbox of my issue.
The issue you met is caused by :key="category.title". Check Vue Guide: key
The key special attribute is primarily used as a hint for Vue’s
virtual DOM algorithm to identify VNodes when diffing the new list of
nodes against the old list. Without keys, Vue uses an algorithm that
minimizes element movement and tries to patch/reuse elements of the
same type in-place as much as possible. With keys, it will reorder
elements based on the order change of keys, and elements with keys
that are no longer present will always be removed/destroyed.
When change the title, the key is changed, then the VNode will be destroyed then created, that is why it lost the focus and it would not lose the focus when changing the summary.
So the fix is uses second parameter=index of v-for as the key instead.
And don't mutate the props directly, so in below snippet, I used one computed setter to emit one input event to inform the parent component update the binding values (by v-model).
Also you can check this question: updating-a-prop-inside-a-child-component-so-it-updates-on-the-parent for more details on updating a prop inside the component.
Vue.component('category-component', {
template:`
<div class="hello">
<input type="text" v-model="innerTitle">
<br>
<textarea v-model="innerSummary"></textarea>
</div>
`,
props: {
value: {
default: () => {return {}},
type: Object
}
},
computed: {
innerTitle: {
get: function () {
return this.value.title
},
set: function (val) {
this.$emit('input', {summary: this.value.summary, title: val})
}
},
innerSummary: {
get: function () {
return this.value.summary
},
set: function (val) {
this.$emit('input', {title: this.value.title, summary: val})
}
}
}
})
new Vue ({
el:'#app',
data () {
return {
categories: [
{
title: "Cat 1",
summary: "description"
},
{
title: "Cat 2",
summary: "description"
},
{
title: "Cat 3",
summary: "description"
}
]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
{{ categories }}
<category-component v-for="(category, index) in categories" :key="index" v-model="categories[index]" />
</div>

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