Reputation: 878
I'm trying to make 3 button controls to open and close 3 corresponding menu lists. Material-UI is the UI framework I'm currently using. Here is the code:
const Filters = () => {
const [anchors, setAnchors] = useState([null, null, null])
const handleClick = (event, index) => {
const arr = anchors
arr[index] = event.target
setAnchors(arr)
}
const handleClose = (index) => {
const arr = anchors
arr[index] = null
setAnchors(arr)
}
return(
<div id="top-filters">
<div className="controls">
<Button onClick={(event) => handleClick(event, 0)}>Sort By</Button>
<Button onClick={(event) => handleClick(event, 1)}>Price</Button>
<Button onClick={(event) => handleClick(event, 2)}>Delivery Fees</Button>
</div>
{
console.log({anchors})
}
<Menu key={1}
anchorEl={anchors[0]}
open={Boolean(anchors[0])}
onClose={() => handleClose(0)}
keepMounted
>
<MenuItem>
<ListItemText primary="By Rating" />
</MenuItem>
</Menu>
<Menu key={2}
anchorEl={anchors[1]}
open={Boolean(anchors[1])}
onClose={() => handleClose(1)}
keepMounted
>
<MenuItem>
<Slider defaultValue={25} marks={marks} step={25} />
</MenuItem>
</Menu>
<Menu key={3}
anchorEl={anchors[2]}
open={Boolean(anchors[2])}
onClose={() => handleClose(2)}
keepMounted
>
<MenuItem>
<ListItemText primary="By Delivery Fees" />
</MenuItem>
</Menu>
<FormControl className="search-bar-filter">
<Icon>search</Icon>
<StyledInput classes={{ root: classes.root }} name="search" type="search" placeholder="Search" disableUnderline />
</FormControl>
</div>
)
}
export default Filters
I checked the values update in console, they look fine, but I'm not sure why React won't rerender the page (when I click the buttons, nothing happens but anchors state is updated). Thanks for help.
Upvotes: 6
Views: 12975
Reputation: 17608
What you are doing there is a mutation:
arr[index] = event.target
You should avoid mutation when updating your state. Because if you mutate your state React can not understand your state has changed. You can use methods like map
, filter
or any other method which do not mutate your state.
const handleClick = (event, index) => {
setAnchors((prev) =>
prev.map((el, i) => {
if (i !== index) {
return el;
}
return event.target;
})
);
};
or if you like a concise one:
const handleClick = (event, index) => {
setAnchors((prev) => prev.map((el, i) => (i !== index ? el : event.target)));
};
The spread syntax can be used for non-nested arrays, but if you are working on nested arrays and changing the nested values just be careful since spread syntax creates shallow copies. This is why I like to to use methods like map
most of the time.
If you don't want to map all over the array (some prefers this) Object.assign
and spread syntax can be used together.
const handleClick = (event, index) => {
setAnchors((prev) => Object.assign([], { ...prev, [index]: event.target }));
};
Update:
As I explained before, spread syntax only makes shallow copies.
Note: Spread syntax effectively goes one level deep while copying an array. Therefore, it may be unsuitable for copying multidimensional arrays, as the following example shows. (The same is true with Object.assign() and spread syntax.)
This means nested values keep the same references as the original ones. So, if you change something for the new array (or object) it also changes the original one.
const arr = [
{ id: 0, name: "foo" },
{ id: 1, name: "bar" },
{ id: 2, name: "baz" },
];
const newArr = [...arr];
newArr[0].name = "something else";
console.log("newArr", newArr);
console.log("original arr", arr);
As you can see our original array also changed. Actually, this isn't a nested array but we are changing a nested property for the array element. Maybe this is a better example for nested arrays, but the example above is more realistic.
const arr = [["foo", "bar"], [1,2]];
const newArr = [...arr];
newArr[0][0] = "fizz";
console.log(newArr);
console.log(arr);
Upvotes: 13
Reputation: 8751
You are wrongly updating the state.
const arr = anchors
is not the correct way to clone the anchors. You need to use ...
operator.
const handleClick = (event, index) => {
const arr = [... anchors]
arr[index] = event.target
setAnchors(arr)
}
const handleClose = (index) => {
const arr = [... anchors]
arr[index] = null
setAnchors(arr)
}
Upvotes: 7