skip to Main Content

Why does this expression only overrides other properties from the array?

const toggleComplete = (id) => {
      const updatedTodos = todos.map((todo) =>
        todo.id === id ? todo.completed = !todo.completed : todo
      );
      setTodos(updatedTodos);
    }

With the previous code, todos array only contains the field completed and deletes all other fields.

const toggleComplete = (id) => {
      const updatedTodos = todos.map((todo) => {
        if (todo.id === id) {
          todo.completed = !todo.completed;
        }
        return todo;
      });
      setTodos(updatedTodos);
    }

While with this code all the array fields stay the same and only changes the completed field, which is the expected behaviour.

3

Answers


  1. The result of .map() is entirely based on what the callback function returns. In the second example it’s always returning the todo object. But in the first example it’s returning this:

    todo.id === id ? todo.completed = !todo.completed : todo
    

    This conditionally returns either the whole todo object or the result of this expression:

    todo.completed = !todo.completed
    

    The result of an assignment expression is the value which was assigned and only that value. So while this expression is performing an assignment which modifies the todo object, that’s of little consequence because that object isn’t returned so .map() doesn’t use it for anything. It only uses the returned value.


    This is reminiscent of a common misuse of the .map() operation. It’s meant to project an array into a new array, it is not meant for looping over an array to modify something in that array. .forEach() or a simple for loop would be used for that.


    As an aside… I recommend not using either approach in React like this. While this is creating a new array to set state, just before doing so it’s also mutating state.

    Don’t assign properties on todo at all. Instead, return from the .map() callback a new object:

    const updatedTodos = todos.map((todo) =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    );
    
    Login or Signup to reply.
  2. Because in the first code you are actually returning ‘todo.completed’ if ‘todo.id’ is the same as ‘id’.
    In a code example you are basically doing this with the ternary operator:

    const toggleComplete = (id) => {
     const updatedTodos = todos.map((todo) => {
      if (todo.id === id) {
       todo.completed = !todo.completed;
       return todo;
      } else {
       return todo;
      }
     });
     setTodos(updatedTodos);
    }
    

    You might want to read about ternary operator https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_operator

    Login or Signup to reply.
  3. Let me correct first code based on your expected output, it will be self explanatory then,

    const toggleComplete = (id) => {
      const updatedTodos = todos.map((todo) => {
        todo.id === id ? todo.completed = !todo.completed : todo
        return todo;
      });
      setTodos(updatedTodos);
    }
    

    If you still don’t get it, here’s the explanation: when you don’t provide curly braces in fat-arrow functions, it will simply return the result of the expression you write (in your case it will always be true), so it was replacing the object in the array.

    In the second part you’re toggling the todo.completed and then returning the todo, so it is working fine.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search