2

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
  });
  • Just because you "store it in a different variable," it doesn't mean that it's a different object, it still references that same object. Also, there's nothing preventing you from doing `this.setState({ this.state.todos })`, however, that's a bad practice and you shouldn't be mutation your `state` properties (your `todos` array, not the `object`s stored within that array). What you're doing might work right now, but it could break in the future because `React` doesn't handle this type of use case properly. – goto Jun 01 '20 at 12:29

3 Answers3

5

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 };
});
Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
0

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
  }
});
Józef Podlecki
  • 10,453
  • 5
  • 24
  • 50
-2

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.

Diego Molina
  • 325
  • 3
  • 8
  • 2
    "The find method returns a new object not a reference to it, therefore returns an immutable object." - this is an incorrect statement. It returns the object that it finds and `JavaScript` objects are not immutable by default unless you make them such. – goto Jun 01 '20 at 12:32