Component
Here is an example of code. What I want is to toggle the active state ( true / false) for each individual list item when I clicked it. I don't want to change them all at once.
Any help with this? Thanks in advance.
import React, { useState } from "react";
const App = () => {
const [active, setActive] = useState({});
const items = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' }
];
handleClick = (index) => {
...
}
const list = items.map( (item, index) => {
return(
<li
key={index}
onClick={() => handleClick(index)}
className={active ? "active" : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
}
export default App;
First option - Introducing isActive property for each elements
I would introduce in the items an active property for each item and manipulate them based on the clicked item. Which needs to be added to useState where you can update the isActive property with .map().
Similarly like the following:
const [items, setItems] = useState([
{ name: 'Item 1', isActive: true },
{ name: 'Item 2', isActive: false },
{ name: 'Item 3', isActive: false }
]);
handleClick = (index) => {
setItems(prevItems =>
prevItems.map((e, i) => ({...e, isActive: i === index}))
);
}
const list = items.map( (item, index) => {
return(
<li
key={index}
onClick={() => handleClick(index)}
className={item.isActive ? 'active' : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
Second option - store the index for the clicked active element
Storing the index for the clicked element helps you the identify in .map() which one is the active one. So with a simple check with i === index you can add active class to the <li> element.
You can create a state for index with useState as the following:
const [index, setIndex] = useState(0);
const items = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' }
];
handleClick = (i) => {
setIndex(i);
}
const list = items.map( (item, i) => {
return(
<li
key={i}
onClick={() => handleClick(i)}
className={i === index ? 'active' : null}
>
{item.name}
</li>
)
})
return {
<>
<ul>{list}</ul>
</>
}
I hope this helps!
Added comments inline.
const App = () => {
// active is active item, initially '' means, nothing selected.
const [active, setActive] = useState("");
const items = [{ name: "Item 1" }, { name: "Item 2" }, { name: "Item 3" }];
const list = items.map(({ name }) => (
<li
key={name /* avoid using index for key */}
onClick={() => setActive(name)}
className={active === name ? "active" : ""}
>
{name}
</li>
));
// return the Element, not {}
return (
<>
<ul>{list}</ul>
</>
);
};
export default App;
Related
I need to set the active classname to multiple onclick items inside a .map
I need the list of active items that were clicked
The items that were clicked will be highlighted in yellow, and when i click the same item again it should be removed from active list items.
const [data, setData] = useState([]);
const [activeIndicies, setActiveIndicies] = useState(() =>
data?.map(() => false)
);
useEffect(() => {
// This data is coming from the API response
const data = [
{ id: 1, name: "one" },
{ id: 2, name: "two" },
{ id: 3, name: "three" }
];
setData(data);
}, []);
return statement
onClick={() => {
setActiveIndicies(
activeIndicies.map((bool, j) => (j === index ? true : bool))
);
}}
Code Sandbox
Thank you.
try this one:
import "./styles.css";
import React, { useState, useEffect } from "react";
export default function App() {
const [data, setData] = useState([
{ id: 1, name: "one", active: false },
{ id: 2, name: "two", active: false },
{ id: 3, name: "three", active: false }
]);
return (
<div className="App">
<h2>Set active className to multiple items on .map</h2>
{data?.map((item, index) => {
return (
<p className={data[index].active ? "selected" : "notselected"}
onClick={() => {
setData((prevState) =>
_.orderBy(
[
...prevState.filter((row) => row.id !== item.id),
{ ...item, active: !item.active }
],
["name"],
["asc"]
)
);
}}
>
{item.name}
</p>
);
})}
</div>
);
}
You can acheive this by simply making some minor changes to your code:
// Changing the state value to an object so that it can
// store the active value for exact item ids
const [activeIndicies, setActiveIndicies] = useState({});
Then inside of .map()
....
// Checking if there is any value for the item id which is being mapped right now.
const selected = activeIndicies[item.id];
return (
<p
className={selected ? "selected" : "notselected"}
onClick={() => {
/* Then setting the state like below where it toggles
the value for particular item id. This way if item is
selected it will be deselected and vice-versa.
*/
setActiveIndicies((prevState) => {
const newStateValue = !prevState[item.id];
return { ...prevState, [item.id]: newStateValue };
});
}}
// Key is important :)
key={item.id}
>
{item.name}
</p>
);
Hello, friends!
I solved this problem in a more convenient way for me )
const data = [
{ id: 1, name: "Ann", selected: true },
{ id: 2, name: "Serg", selected: false },
{ id: 3, name: "Boris", selected: true },
];
//you don't even need to have a boolean field in the object -
//it will be added by itself on the first click on the element
// const data = [{ name:"Ann", id:1}, { name:"Serg", id:2 },{ name:"Boris", id:3 },]
const [users, setUsers] = useState(data); // no square brackets-[data]
function handleActive(item) {
setUsers((prev) => {
return prev.map((itemName) => {
if (itemName.name === item.name) {
return { ...itemName, selected: !itemName.selected };
// itemName.selected = !itemName.selected // or so
}
return itemName;
});
});
}
return (
{
users.map((item, i) => {
// let current = item.selected === true
// or so -> className={current === true ? 'users': ''}
return (
<div
onClick={() => handleActive(item)}
className={item.selected === true ? "active" : ""}
key={i}
>
{item.name}
</div>
);
})
}
);
My program is fake chat App, using useEffect, useState, it will emit a comment per 2 second and display all comments on each lesson. In the first render, it run correctly(emit a comment per 2 second), but in each re-render, it emit 2 comments per 2 second.
I try to console.log(newLessonComments[lessonId - 1].comments.length) after push a comment. And it only push 1 comment to the array.
What is the problem?
import { useState, useEffect } from 'react';
function Content() {
let lessons = [
{
id: 1,
title: 'Bài học 1'
},
{
id: 2,
title: 'Bài học 2'
},
{
id: 3,
title: 'Bài học 3'
}
];
const [lessonId, setLessonId] = useState(1);
const [lessonComment, setLessonComment] = useState(() => {
return lessons.map(lesson => {
return {
id: lesson.id,
comments: []
}
});
})
useEffect(() => {
const handleComment = ({ detail }) => {
setLessonComment(prev => {
const newLessonComments = prev.slice(0);
newLessonComments[lessonId - 1].comments.push(detail);
return newLessonComments;
});
}
window.addEventListener(`lesson${lessonId}`, handleComment);
return () => {
window.removeEventListener(`lesson${lessonId}`, handleComment);
}
}, [lessonId])
return (
<div>
<ul>
{lessons.map(lesson => {
return (
<li
key={lesson.id}
style={{
color: lessonId == lesson.id ? 'red' : '#333',
cursor: 'pointer'
}}
onClick={() => { setLessonId(lesson.id) }}
>
{lesson.title}
</li>
)
})}
</ul>
<div className="comments">
<ul>
{lessonComment[lessonId - 1].comments.map((comment, index) => {
return (
<li
key={index}
>
{comment}
</li>
)
})}
</ul>
</div>
</div>
)
}
export default Content;
function emitComments(id) {
setInterval(() => {
console.log('emit comments')
window.dispatchEvent(
new CustomEvent(`lesson${id}`, {
detail: `Nội dung comments của lesson ${id}`
})
)
}, 2000)
}
emitComments(1);
emitComments(2);
emitComments(3);
I have a problem. I want to make buttons section, where user can click buttons to filter some content. When user click on 'all' button, all other should be turn off (change its color to initial, not active) in this moment. Also, user can check multiple buttons.
I can't get how to do this.
Example of JSON:
{
title: 'All',
id: 53,
},
{
title: 'Im a parent',
icon: <Parent />,
id: 0,
},
{
title: 'I live here',
icon: <ILiveHere />,
id: 2,
},
example of code: https://codesandbox.io/s/sleepy-haze-35htx?file=/src/App.js
Its wrong, I know. I tried some solutions, but I guess I can't get how to do it correctly.
With this code I can do active multiple buttons, but I can't get how to make conditions like
if (item.title === 'all){
TURN_OFF_ANY_OTHER_BTNS
}
I guess I should store checked buttons in temporary array to make these operations.
Will be really thankfull for help.
Is this something you would like?
const SocialRole = ({ item, selected, setSelected }) => {
let style =
[...selected].indexOf(item.id) !== -1
? { color: "red" }
: { color: "blue" };
return (
<button
style={style}
onClick={() => {
if (item.id === 53) {
setSelected(null);
} else {
setSelected(item.id);
}
}}
>
{item.icon}
<h1>{item.title}</h1>
</button>
);
};
export default function App() {
// We keep array of selected item ids
const [selected, setSelected] = useState([roles[0]]);
const addOrRemove = (item) => {
const exists = selected.includes(item);
if (exists) {
return selected.filter((c) => {
return c !== item;
});
} else {
return [...selected, item];
}
};
return (
<div>
{roles.map((item, index) => (
<SocialRole
key={index}
item={item}
selected={selected}
setSelected={(id) => {
if (id === null) setSelected([]);
else {
setSelected(addOrRemove(id));
}
}}
/>
))}
</div>
);
}
If I understand your problem, I think this is what you are looking for:
const roles = [
{
title: "All",
id: 53
},
{
title: "I live here",
id: 0
},
{
title: "I live here too",
id: 2
}
];
// button
const SocialRole = ({ item, selected, setSelected }) => {
const isActive = selected === item.title || selected === 'All';
return (
<button
style={isActive ? { color: "red" } : { color: "blue" }}
onClick={() => setSelected(item.title)}
>
{item.icon}
<h1>{item.title}</h1>
</button>
);
};
export default function App() {
const [selected, setSelected] = useState(roles[0].title);
return (
<div>
{roles.map((item, index) => (
<SocialRole
key={index}
item={item}
selected={selected}
setSelected={setSelected}
/>
))}
</div>
);
}
The problem was you were setting a new state into each button, when you should just use the state from the App.
I rendered a list of buttons using Array.map method in a function component. When I tried to pass state to each mapped array items, the rendered results changed all array items at once, instead of one by one.
Here is my code. Am I doing something wrong? Sorry if the question has been solved in other thread or I used the wrong method. This is my first React project and I am still learning. It would be very appreciated if someone could advise. Thank you!
import React, { useState } from "react"
export default function Comp() {
const [isActive, setActive] = useState(false)
const clickHandler = () => {
setActive(!isActive)
console.log(isActive)
}
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => {
return items.map(item => (
<li key={item.id}>
<button onClick={clickHandler}>
{item.name} {isActive ? "active" : "not active"}
</button>
</li>
))
}
return (
<ul>{renderList(data)}</ul>
)
}
Put the individual item into a different component so that each has its own active state:
export default function Comp() {
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => (
items.map(item => <Item key={item.id} name={item.name} />)
);
return (
<ul>{renderList(data)}</ul>
)
}
const Item = ({ name }) => {
const [isActive, setActive] = useState(false);
const clickHandler = () => {
setActive(!isActive);
};
return (
<li>
<button onClick={clickHandler}>
{name} {isActive ? "active" : "not active"}
</button>
</li>
);
};
You need to set the active-id in handling the click-event. That will in-turn render active/non-active conditionally:
Notice the flow (1) > (2) > (3)
function Comp() {
const [activeId, setActiveId] = React.useState(null);
const clickHandler = (item) => {
setActiveId(item.id) // (2) click-handler will set the active id
}
const data = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
]
const renderList = items => {
return items.map(item => (
<li key={item.id}>
<button onClick={() => clickHandler(item)}> // (1) passing the clicked-item so that we can set the active-id
{item.name} {item.id === activeId ?
"active" : "not active" // (3) conditionally render
}
</button>
</li>
))
}
return (
<ul>{renderList(data)}</ul>
)
}
Good Luck...
Still learning some ropes of React.
I have the following code where I render a list of buttons:
import React, { useState, useEffect } from 'react';
const MyComponent = (props) => {
const [buttonList, setButtonList] = useState([]);
useEffect(() => { getButtonList()}, [])
const getButtonList = () => {
let data = [
{id: 1, name: 'One', selected: false },
{id: 2, name: 'Two', selected: false },
{id: 3, name: 'Three', selected: false }
]
setButtonList(data)
}
const ButtonItem = ({ item }) => {
const btnClick = (event) => {
const id = event.target.value
buttonList.forEach((el) => {
el.isSelected = (el.id == id) ? true : false
})
setButtonList(buttonList)
console.log('buttonList', buttonList)
}
return (
<button type="button"
className={ "btn mx-2 " + (item.isSelected ? 'btn-primary' : 'btn-outline-primary') }
onClick={btnClick} value={item.id}>
{item.name + ' ' + item.isSelected}
</button>
)
}
return (
<div className="container-fluid">
<div className="card mb-3 rounded-lg">
<div className="card-body">
{
buttonList.map(item => (
<ButtonItem key={item.id} item={item} />
))
}
</div>
</div>
</div>
)
}
export default MyComponent;
So the button renders:
[ One false ] [ Two false ] [ Three false ]
And when I click on any Button, I can see on Chrome React Tools that the value for isSelected of that button becomes true. I can also confirm that the specific array item for the button clicked in the dev tools for State (under hooks), the value is true.
The text for the button clicked does not show [ One true ] say if I clicked on button One. What am I missing here?
P.S. Note that I also want to change the class of the button, but I think that part will be resolved if I get the button isSelected value to be known across the component.
Code Demo:
https://codesandbox.io/s/laughing-keller-o5mds?file=/src/App.js:666-735
Issue: You are mutating the state object instead of returning a new state reference. You were also previously using === to compare a string id to a numerical id which was returning false for all comparisons.
Solution: Use a functional update and array.map to update state by returning a new array.
const ButtonItem = ({ item }) => {
const btnClick = event => {
const id = event.target.value;
setButtonList(buttons =>
buttons.map(button => ({
...button,
isSelected: button.id == id
}))
);
};
...
};
Suggestion: Factor out the btnClick handler, it only needs to be defined once. Curry the id property of item so you can use ===.
const btnClick = id => event => {
setButtonList(buttons =>
buttons.map(button => ({
...button,
isSelected: button.id === id
}))
);
};
Update the attaching of click handler to pass the item id
const ButtonItem = ({ item }) => {
return (
<button
type="button"
className={
"btn mx-2 " +
(item.isSelected ? "btn-primary" : "btn-outline-primary")
}
onClick={btnClick(item.id)} // <-- pass item.id to handler
value={item.id}
>
{item.name + " " + item.isSelected}
</button>
);
};
In your btnClick handler you are mutating your state, you should create a new value and assign it instead:
import React from "react";
import "./styles.css";
import { useState, useEffect } from "react";
const ButtonItem = ({ item, onClick }) => {
return (
<button
type="button"
className={
"btn mx-2 " + (item.isSelected ? "btn-primary" : "btn-outline-primary")
}
onClick={() => onClick(item.id)}
value={item.id}
>
{item.name + " " + item.isSelected}
</button>
);
};
const MyComponent = props => {
const [buttonList, setButtonList] = useState([]);
useEffect(() => {
getButtonList();
}, []);
const getButtonList = () => {
let data = [
{ id: 1, name: "One", isSelected: false },
{ id: 2, name: "Two", isSelected: false },
{ id: 3, name: "Three", isSelected: false }
];
setButtonList(data);
};
const btnClick = id => {
const updatedList = buttonList.map(el => ({
...el,
isSelected: el.id === id
}));
setButtonList(updatedList);
console.log("buttonList", updatedList);
};
return (
<div className="container-fluid">
<div className="card mb-3 rounded-lg">
<div className="card-body">
{buttonList.map(item => (
<ButtonItem key={item.id} item={item} onClick={btnClick} />
))}
</div>
</div>
</div>
);
};
export default MyComponent;