Why are we using ...prevState when we are going to delete?

lesson : lesson

  handleDelete(taskIdToRemove){
    this.setState((prevState) => ({
      ...prevState,
      allTasks: prevState.allTasks.filter((task) => task.id !== taskIdToRemove)
    }));
  }

why are we including the …prevState of we are going to delete an item from the state . Doesn’t it include all the previous state values?

You’re right, ...prevState isn’t necessary here because setState() does shallow merging with the existing state, so the newTask object that is also in state would still be there.

It could also be partially removed from here:

  handleChange({ target }){
    const { name, value } = target;
    this.setState((prevState) => ({
      ...prevState, // <-- this can be removed. allTasks will be preserved
      newTask: {
        ...prevState.newTask, // <-- this cannot be removed
        [name]: value,
        id: Date.now()
      }
    }));
  }

The one I commented with “this cannot be removed” requires the spread of the previous values in order for the other properties of the newTask object to survive the update.

1 Like

I have two questions.

1. I want to know why we are actually using ...prevState in handleDelete if we want to delete the data . Won’t that include all the previous data which we might want to delete too?

2.I do have a doubt about this handleChange too . Let’s assume we are not removing that ...prevState . So what happens when the newTask from the ...prevState and the ...prevState.newTask have the same data .

The use of ...prevState in handleDelete had no impact at all in this case. newTask is maintained regardless and the allTasks array is being updated with a new value. It wasn’t a true delete where it’s trying to delete everything from state, it’s just removing an item from the allTasks array.

However, if …prevState had been included last, then it would have overridden any updated values (like the removal of an item from allTasks). Whatever comes last overrides any previous key/value pairs with the same name, which is why including ...prevState had no impact on the array - the allTasks in ...prevState is overridden by the new value you’re setting for allTasks because it came after.

It makes no difference at all that the ...prevState is there or not. If it is there, then it will get overridden by the newTask that comes after it. allTasks will still be in the state without using the spread too. The only thing that mattered was that ...prevState.newTask was included because the entire newTask object is being replaced so if an existing key/value pair wasn’t represented in the updated newTask object, then it would be lost. The spread allows it to maintain existing key/values and the ones that come after it with the same name will override.

All of their existing code for the class-based component works as is. Whether it includes extra unnecessary spreads or not, it does work and you wouldn’t have any issues of things not being updated.


I know this lesson is all about converting an existing class-based component to using React Hooks, where you don’t need to use setState(), but it has raised some questions for you and some others. Here is some code to explore shallow merging, very similar to how React merges the updates with the state in setState(). We’ll use Object.assign() to simulate it and you can modify it to do tests of your own.

const state = {
  newTask: { test: true },
  allTasks: []
};

//Call #1 - set the value of allTasks to an array
Object.assign(state, {
  allTasks: [1, 2, 3, 4, 5, 6, 7]
});
console.log('\nAfter #1 state = \n', state);
// newTask still there, despite the spread not being here


//Call #2 - filter out the value 1 from allTasks
Object.assign(state, {
  ...state,
  allTasks: state.allTasks.filter(value => value !== 1)
});
console.log('\nAfter #2 state = \n', state); // 1 removed from allTasks


//Call #3 - filter out the value 2 from allTasks
Object.assign(state, {
  allTasks: state.allTasks.filter(value => value !== 2)
});
console.log('\nAfter #3 state = \n', state); // 2 removed from allTasks


//Call #4 - attempt to filter out the value of 3 from allTasks
Object.assign(state, {
  allTasks: state.allTasks.filter(value => value !== 3),
  ...state
});
console.log('\nAfter #4 state = \n', state);
// 3 was NOT removed from allTasks due to the position of the spread


//Call #5 - change the 'test' value in the newTask object
Object.assign(state, {
  ...state,
  newTask: { test: false }
});
console.log('\nAfter #5 state = \n', state);
// newTask updated, allTasks still there


//Call #6 - change the 'test' value in the newTask object
Object.assign(state, {
  newTask: { test: true }
});
console.log('\nAfter #6 state = \n', state);
// newTask updated again, allTasks still there despite the lack of spread


//Call #7 - add the 'note' property in the newTask object
Object.assign(state, {
  newTask: { 
    ...state.newTask,
    note: 'I like testing'
   }
});
console.log('\nAfter #7 state = \n', state);
// newTask updated to include a new property. 'test' property is maintained
//  because of the use of the spread operator. allTasks still there without
//  needing the spread


//Call #8 - change the 'test' value in the newTask object
Object.assign(state, {
  newTask: { test: false }
});
console.log('\nAfter #8 state = \n', state);
// newTask 'test' value updated, but we lost the 'note' property entirely
//  because we didn't use the spread in the newTask object


//Call #9 - adding back the 'note' property to newTask and attempting to
// update the value of 'test' again
Object.assign(state, {
  newTask: {
    test: true,
    note: 'I like testing',
    ...state.newTask
  }
});
console.log('\nAfter #9 state = \n', state);
// we added the 'note' property again, but we failed to update the value 
//  of 'test' because of our position of the spread.


//Call #10 - let's update everything at once
Object.assign(state, {
  newTask: {
    ...state.newTask,
    note: 'testing overload',
    status: 'learning'
  },
  allTasks: state.allTasks.filter(value => value % 3)
});
console.log('\nAfter #10 state = \n', state);
// 'test' maintained in newTask, 'note' updated, 'status' added
// allTasks now only contains values that aren't divisible by 3

Output from above:

After #1 state =
 { newTask: { test: true }, allTasks: [ 1, 2, 3, 4, 5, 6, 7 ] }

After #2 state =
 { newTask: { test: true }, allTasks: [ 2, 3, 4, 5, 6, 7 ] }

After #3 state =
 { newTask: { test: true }, allTasks: [ 3, 4, 5, 6, 7 ] }

After #4 state =
 { newTask: { test: true }, allTasks: [ 3, 4, 5, 6, 7 ] }

After #5 state =
 { newTask: { test: false }, allTasks: [ 3, 4, 5, 6, 7 ] }

After #6 state =
 { newTask: { test: true }, allTasks: [ 3, 4, 5, 6, 7 ] }

After #7 state =
 { newTask: { test: true, note: 'I like testing' },
  allTasks: [ 3, 4, 5, 6, 7 ] }

After #8 state =
 { newTask: { test: false }, allTasks: [ 3, 4, 5, 6, 7 ] }

After #9 state =
 { newTask: { test: false, note: 'I like testing' },
  allTasks: [ 3, 4, 5, 6, 7 ] }

After #10 state =
 { newTask:
   { test: false, note: 'testing overload', status: 'learning' },
  allTasks: [ 4, 5, 7 ] }