I have a JSON file with an array of objects and each object consists of "Question" and "Answer" (I'm creating an FAQ section). What I'm doing is mapping over the array and displaying the list of questions, which works just fine. Next to each question is an icon and I want the icon to change when I click on it but it is changing EVERY icon in the list instead of just that one item that was clicked on.
I'm using Material UI and hooks and this is how I have my handleClick set up:
const [click, setClick] = useState(true);
const handleClick = () => {
setClick(!click);
};
This is how I have the array mapping set up:
<List
style={{
maxHeight: 430,
width: 500,
overflow: 'auto',
border: '1px solid black',
}}
>
{faqdata.map((item) => (
<ListItem style={{ cursor: 'pointer' }}>
<ListItemIcon>
{click ? <AddIcon /> : <RemoveIcon />}
</ListItemIcon>
<ListItemText primary={item.Question} onClick={handleClick} />
</ListItem>
))}
</List>
How can I make it to where the icon changes on only the list item that I click on instead of every list item in the list? Is my onClick in the incorrect spot? Any help would be greatly appreciated. Thanks!
You are using a single boolean value to store a "clicked" state, and all your mapped UI uses that single state to cue from.
Assuming you would like multiple items to be clicked, and also assuming your mapped data is static (i.e. the faqData isn't added to, removed from, or sorted) then using the mapped index to toggle the "clicked" state is acceptable. use an object to store "clicked" indices and update the handleClick callback to toggle the state. For this use case I like to make the callback a curried handler to enclose in scope the value I wish to use in the callback.
const [clickedIndex, setClickedIndex] = useState({});
const handleClick = (index) => () => {
setClickedIndex(state => ({
...state, // <-- copy previous state
[index]: !state[index] // <-- update value by index key
}));
};
...
<List
style={{
maxHeight: 430,
width: 500,
overflow: 'auto',
border: '1px solid black',
}}
>
{faqdata.map((item, index) => (
<ListItem style={{ cursor: 'pointer' }}>
<ListItemIcon>
{clickedIndex[index] ? <AddIcon /> : <RemoveIcon />} // <-- check if index is truthy in clickedIndex state
</ListItemIcon>
<ListItemText
primary={item.Question}
onClick={handleClick(index)} // <-- pass index to handler
/>
</ListItem>
))}
</List>
Is this what you're looking for?
import { useState } from "react";
import "./styles.css";
const faqdata = [
{ Question: "Q1", Answer: "A1" },
{ Question: "Q2", Answer: "A2" },
{ Question: "Q3", Answer: "A3" },
{ Question: "Q4", Answer: "A4" }
];
const AddIcon = () => <span class="icon">+</span>;
const RemoveIcon = () => <span class="icon">☓</span>;
function ListItem({ d }) {
const [checked, setChecked] = useState(false);
return (
<li
onClick={() => {
setChecked(!checked);
}}
>
{checked ? <RemoveIcon /> : <AddIcon />}
{d.Question}
</li>
);
}
function List() {
return (
<ul>
{faqdata.map((d) => {
return <ListItem d={d} />;
})}
</ul>
);
}
You can try it out here
The problem with the current approach is that there's only one variable to store the added/removed status of every question. So, when the click boolean updates, it updates the state of all elements.
In the code shared above, the ListItem component is responsible for maintaining the added/removed status of each question separately. So, one item in the list can change without affecting the other.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With