Jan Hesters

Updater Functions in React's setState

I recently learned a little nugget about how functions in setState work.

This article is for you if you are unaware of the differences between these lines (especially between 3 and 5):

this.setState({ on: !this.state.on });
// ,
this.setState(prevState => ({ ...prevState, on: !prevState.on }));
// , or
this.setState(({ on }) => ({ on: !on }));
// šŸ¤”

I learned (and I'm still learning) React using video lectures. In most courses about React I watched the instructor updating state like this:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
 
const styles = {
  container: {
    fontFamily: 'sans-serif',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
  },
};
 
class StateExample extends Component {
  state = { on: false };
 
  toggleLight = () => {
    this.setState({ on: !this.state.on });
  };
 
  render() {
    const { on } = this.state;
    return (
      <div style={styles.container}>
        The light is on {`The light is ${on ? 'onšŸ’”' : 'off šŸŒƒ'}`}
        <button onClick={this.toggleLight}>Toggle light</button>
      </div>
    );
  }
}
 
const rootElement = document.getElementById('root');
ReactDOM.render(<StateExample />, rootElement);

You can copy and paste this code to CodeSandBox. Notice how setState gets an object as its parameter. This is okay for most state. However, in this example the next value of this.state.on depends on the current value of this.state.on.

The recommended way of updating state in React based on previous state is using updater functions instead of objects. I first read this in ā€œUsing a function in setState instead of an objectā€ by Sophia, which is a great article šŸ‘šŸ».

Consequently, for months Iā€™ve been updating state which depends on previous state, using a function and the prevState parameter.

this.setState(prevState => ({ ...prevState, on: !prevState.on }));

This always worked for me and I havenā€™t run into any problems. Then I watched Kent C. Dodds using a function in setState in conjunction with destructuring.

this.setState(({ on }) => ({ on: !on }));

What is the different between these two? I experimented with both, and I couldnā€™t discover a difference.

It turns out it comes down to the inner workings of React. setState calls another function called enqueueSetState.

// In react/src/ReactBaseClasses.js
Component.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  // Here enqueueSetState is called.
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
 
// In react/packages/react-test-renderer/src/ReactShallowRenderer.js
enqueueSetState(publicInstance, partialState, callback, callerName) {
  this._enqueueCallback(callback, publicInstance);
  const currentState = this._renderer._newState || publicInstance.state;
 
  if (typeof partialState === 'function') {
    partialState = partialState.call(
      publicInstance,
      currentState,
      publicInstance.props,
    );
  }
 
  // Null and undefined are treated as no-ops.
  if (partialState === null || partialState === undefined) {
    return;
  }
 
  this._renderer._newState = {
    ...currentState,
    ...partialState,
  };
 
  this._renderer.render(this._renderer._element, this._renderer._context);
}

As you can see here, currentState and partialState are being spread out. Or as the docs put it:

ā€œState Updates are Mergedā€

It follows that using prevState always results in

state = {
  ...prevState,
  ...{ ...prevState, expanded: !prevState.expanded },
};

, whereas Kentā€™s way of destructuring state results in

state = { ...prevState, { expanded: !expanded } };

, which is arguably better.

One more thing: You can see the benefits of destructuring the state even more if you have nested objects in your state.

state = { nested: { on: false, hangingFromCeiling: true } };
 
this.setState(({ nested }) = ({ nested: { ...nested, on: !on } }));

Here we keep our light bulb hanging from the ceiling by destructuring and spreading out the nested key.

I hope you learned something interesting here today šŸŽ“.

Learn senior fullstack secrets

Subscribe to my newsletter for weekly updates on new videos, articles, and courses. You'll also get exclusive bonus content and discounts.