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 š.