Search code examples
javascriptreactjsstatereact-hooksreact-state-management

What exactly is prevState in a functional component's setState?


Returning the (changed) previous state within a setState that one gets from the useState hook doesn't seem to alter state. Run the following straight forward snippet

function App(){
  const [state, setState] = React.useState([{x: 0}])
  function changeCount(){
    setState(prevState => {
      console.log('before', prevState[0])
      const newState = [...prevState]
      newState[0].x += 1 //the shallow copy newState could potentially change state
      console.log('after', prevState[0])//here x gets bigger as expected
      return prevState //instead of newState we return the changed prevState
    })
  }
  //checking state shows that x remained 0
  return <div className='square' onClick={changeCount}>{state[0].x}</div>
}
ReactDOM.render(<App/>, document.getElementById('root'))
.square{
  width: 100px;
  height: 100px;
  background: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<div id='root'></div>
By clicking the square we trigger setState. Within setState we make a shallow copy newState of the previous state. By changing the copy we change the prevState (maybe unintentionally) as the console confirms. Returning the changed previous state form setState however doesn't change the state as the count remains 0. If we were to return the newState, the behavior would be as expected.

Repeating this, shows that prevState gets bigger, it just doesn't seem to represent the previous state anymore.

Why is that? I made this minimal example on codepen...


Solution

  • Consider that object assignment is just a reference assignment, never a copy

    obj1 = {x:42, y:99};
    obj2 = obj1;   // obj1 and obj2 both reference the same object
    obj1.x += 1;   
    obj2.y += 1;
    console.log(obj1.x, obj1.y);
    console.log(obj2.x, obj2.y);  // prints the same thing since obj1 and obj2 are the same object
    

    In the above example, obj1 is initialized to point to a new object with properties x and y. When obj2=obj1 is made, this is not a copy of obj1 into obj2, but rather obj1 and obj2 now reference the same object.

    Hence, when the console.log statements print, they print the same thing because they are both printing property values from the same object.

    Similarly, the additional reference to the original object is being made when the shallow copy from prevState to newState occurrs.

    obj = {x:42, y:99};
    prevState[0] = obj;     // prevState[0] is a reference to obj.  prevState[0] and obj point to the same exact thing
    
    newState = [...prevState];  // shallow copy, but newState[0] is an object reference.  newState[0] and prevState[0] both point to "obj"
    
    newState[0].x += 1;         // is actually updating the original object assigned to prevState[0]