Reputation: 351
I want to make checkboxes controlled, handleChange does the job and toggles the completed property but react doesn't rerender the dom I suppose. How do I make it work?
import React from "react";
export default function App() {
const [items, setItems] = React.useState([
{ title: "Item 1", completed: false, id: 1 },
{ title: "Item 2", completed: false, id: 2 },
{ title: "Item 3", completed: false, id: 3 }
]);
function handleChange(index) {
const itemsRef = items;
itemsRef[index].completed = !itemsRef[index].completed;
setItems(itemsRef);
}
return (
<div id="app">
<ul>
{items.map((item, index) => (
<li key={item.id}>
<input
type="checkbox"
checked={item.completed}
onChange={() => handleChange(index)}
/>
<label>{item.title}</label>
</li>
))}
</ul>
</div>
);
}
Upvotes: 0
Views: 865
Reputation: 11800
useState
is not like this.setState
and will not re-render on mutations, you have to create a copy of the array before you mutate it
function handleChange(index) {
const itemsRef = [...items];
itemsRef[index].completed = !itemsRef[index].completed;
setItems(itemsRef);
}
This is enough for React to re-render even if we mutate the inner object.
But, it is recommended to use the callback pattern and do a complete immutable update if you need to compute the next state based on the previous state because useState
updates are always asynchronous
function handleChange(index) {
setItems(prevState => prevState.map((obj, i) =>
i === index
// create a new object and spread all of the properties
// then toggle the completed value based on the previous state
? { ...obj, completed: !obj.completed }
: obj
))
}
You can also write the above like this
function handleChange(index) {
setItems(prevState => [
...prevState.slice(0, index),
// create a new object and spread all of the properties
// then toggle the completed value based on the previous state
{
...prevState[index],
completed: !prevState[index].completed
},
...prevState.slice(index + 1)
])
}
Upvotes: 2