Reputation: 91
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
Upvotes: 2
Views: 1311
Reputation: 202608
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>
);
};
Upvotes: 3
Reputation: 9814
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;
Upvotes: 0