Mutable state update in react

Viewed 3977

For example, we have a state defined like this which is having some todos and we want to change done of that todo if they are completed

this.state = {
  // store an array of todo objects
  todos: [
    { task: 'do the dishes', done: false, id: 1 },
    { task: 'vacuum the floor', done: true, id: 2 }
  ]
};

For that, we can do this away.

  const theTodo = this.state.todos.find(t => t.id === id);
  theTodo.done = true; // NOOOOO

  this.setState({
    todos: this.state.todos
  });

}

But I am a little confused that find method on the array will return us the element which matches the condition inside it and we store it in a different variable and then access done to change it to true. But still, we haven't changed the original state value as this was a copy one.

Then how come we can use this to save it?

 this.setState({
    todos: this.state.todos // bad
  });
3 Answers

The reason the code works is because State Updates are Merged.

When you call setState(), React merges the object you provide into the current state.

But, it is just a bad practice You shouldn't mutate this.state directly.

Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

For completeness, just pass a function to this.setState and use map to save the immutablillity rule.

Passing an update function allows you to access the current state value inside the updater.

this.setState((prevState) => {
  const todos = prevState.todos.map((todo) =>
    todo.id === id ? { ...todo, done: true } : todo
  );
  return { todos };
});

Just spread everything


this.setState(oldState => {
  const todos = oldState.todos;
  const todoIndex = todos.findIndex(pr => pr.id === id);
  const todo = todos[todoIndex];
  const newTodos = [...todos.slice(0, todoIndex), {...todo, done: true}, ...todos.slice(todoIndex + 1)]

  return {
    ...oldState,
    todos
  }
});

This is not an issue or something related to React framework. It's a basic understanding of JavaScript. Of course you will get a match, the find method is applicable to any array and your todo is an array. The find method returns a new object not a reference to it. If you want to update just that, create a copy of your todo, then use any method to update the right todo item, and then add the copied todo to the new state. You could use immer, or use spread operators, there's several ways to do this.

Related