I'm having some trouble with the React useState hook. I have a todolist with a checkbox button and I want to update the 'done' property to 'true' that has the same id as the id of the 'clicked' checkbox button. If I console.log my 'toggleDone' function it returns the right id. But I have no idea how I can update the right property.
The current state:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: false
},
]
})
const toggleDone = (id) => {
console.log(id);
}
return (
<div className="App">
<Todos todos={state.todos} toggleDone={toggleDone}/>
</div>
);
}
The updated state I want:
const App = () => {
const [state, setState] = useState({
todos:
[
{
id: 1,
title: 'take out trash',
done: false
},
{
id: 2,
title: 'wife to dinner',
done: false
},
{
id: 3,
title: 'make react app',
done: true // if I checked this checkbox.
},
]
})
myArray. push(1); However, with React, we need to use the method returned from useState to update the array. We simply, use the update method (In our example it's setMyArray() ) to update the state with a new array that's created by combining the old array with the new element using JavaScript' Spread operator.
To update an object's property in an array of objects, use the map() method to iterate over the array. On each iteration, check if the current object is the one to be updated. If it is, modify the object and return the result, otherwise return the object as is.
You can safely use javascript's array map functionality since that will not modify existing state, which react does not like, and it returns a new array. The process is to loop over the state's array and find the correct id. Update the done
boolean. Then set state with the updated list.
const toggleDone = (id) => {
console.log(id);
// loop over the todos list and find the provided id.
let updatedList = state.todos.map(item =>
{
if (item.id == id){
return {...item, done: !item.done}; //gets everything that was already in item, and updates "done"
}
return item; // else return unmodified item
});
setState({todos: updatedList}); // set state to new object with updated list
}
Edit: updated the code to toggle item.done
instead of setting it to true.
You need to use the spread operator like so:
const toggleDone = (id) => {
let newState = [...state];
newState[index].done = true;
setState(newState])
}
D. Smith's answer is great, but could be refactored to be made more declarative like so..
const toggleDone = (id) => {
console.log(id);
setState(state => {
// loop over the todos list and find the provided id.
return state.todos.map(item => {
//gets everything that was already in item, and updates "done"
//else returns unmodified item
return item.id === id ? {...item, done: !item.done} : item
})
}); // set state to new object with updated list
}
const toggleDone = (id) => {
console.log(id);
// copy old state
const newState = {...state, todos: [...state.todos]};
// change value
const matchingIndex = newState.todos.findIndex((item) => item.id == id);
if (matchingIndex !== -1) {
newState.todos[matchingIndex] = {
...newState.todos[matchingIndex],
done: !newState.todos[matchingIndex].done
}
}
// set new state
setState(newState);
}
All the great answers but I would do it like this
setState(prevState => {
...prevState,
todos: [...prevState.todos, newObj]
})
This will safely update the state safely. Also the data integrity will be kept. This will also solve the data consistency at the time of update.
if you want to do any condition do like this
setState(prevState => {
if(condition){
return {
...prevState,
todos: [...prevState.todos, newObj]
}
}else{
return prevState
}
})
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