This article is aimed at people who have already had their first approach to React, and who, as beginners, have doubts about how setState
works and how to use it correctly. It should also help mid to senior devs use cleaner and more abstracted ways of setting state, and make higher-order-functions handle and abstract state.
Just read and have fun!
So grab a cup of coffee and keep reading! ?
Basic Concepts of setState( )
React Components let you split the user interface (UI) into independent, reusable pieces, so you can think about each piece in isolation.
Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen.
If you need to give the user the opportunity to input something or in some way change the variables the component is receiving as props, you’ll need setState
.
Whether you declare a Component as a function or a class, it must never modify its own props.
All React Components must act like pure functions with respect to their props. This means functions that never try to change their inputs and always return the same result for the same inputs.
Of course, application UIs are dynamic and change over time. That’s why state
was created.
State
allows React components to change their output over time in response to user actions, network responses, and anything else, without violating this rule.
Components defined as classes have some additional features. Local state is a feature available only to class Components.
setState
is the API method provided with the library so that the user is able to define and manipulate state over time.
Three Rules of Thumb When Using setState( )
Do Not Modify State Directly
State Updates May Be Asynchronous
React may batch multiple setState()
calls into a single update for performance.
Because this.props
and this.state
may be updated asynchronously, you should not rely on their values for calculating the next state.
You should always do this kind of manipulation with a functional approach, supplying the state
and props
and returning the new state
based on the former.
State Updates are Merged
When you call setState()
, React merges the object you provide into the current state
.
In the example below, we’re updating the variable dogNeedsVaccination
independently of the other state
variables.
The merging is shallow, so this.setState({ dogNeedsVaccination: true })
leaves the other variables intact, replacing only the value of dogNeedsVaccination
.
Respect the Data Flow and Avoid State the Max
Data flows down! Neither parent nor child components can know if a certain component is stateful or stateless, and they shouldn’t care whether it is defined as a function or a class.
That’s why state
is often called local or encapsulated. It is not accessible to any component other than the one that owns and sets it.
When you setState
a prop and use it in your component, you’re breaking the flow of the rendering props. If for some reason the prop passed into your component changed in the parent component, the child will not re-render auto-magically ?!
Let’s check an example:
Here you have a Home
Component which is generating a magic number each 1000ms and setting it into its own state
.
After that it renders the number and invokes three Child
Components (Siblings) that will receive the magic number with the objective of displaying it using three different approaches:
First Approach
Component ChildOfHome
is respecting the React props cascade flow and, considering that the objective is only to show the magic number, it’s rendering the props
received directly.
Second Approach
Component ChildOfHomeBrother
receives the props
from its parent and, invoking componentDidMount
, sets the magic number into state
. Then it renders the state.magicNumber
.
This example doesn’t work because render()
doesn’t know that a prop
has changed so it is not triggering the re-rendering of the component. As the component is not re-rendered anymore, componentDidMount
is not invoked and the display is not updated.
Third Approach
Usually when we try to make it work using the second approach we think something is missing. Instead of taking a step back we keep on adding stuff to the code to make it work!
So in this third approach we’ve added componentDidUpdate
to check if there’s a change in props
to trigger the re-rendering of the component. This is unnecessary and leads us to unclean code. It also brings with it performance costs that will be multiplied by the number of times we do this in a big App where we have a lot of chained Components and side effects.
This is wrong unless you need to allow the user to change the prop value received.
If you don’t need to change the prop value, always try to keep things working according to the React flow (First Approach).
You can check a working webpage with this example I’ve prepared for you in Glitch. Take a look and have fun ?
Also check out the code in the Home.js
and HomeCodeCleaned.js
(without the HTML stuff) in my repo about this article.
How to setState
So at this point I think it’s time to get our hands dirty!
Let’s play a little bit with setState
and improve on that! Just follow along and grab another cup of coffee!
Let’s create a small form to update user data:
Here’s the code for the example above:
We are setting state
as an object, and there’s no problem because our current state doesn’t depend on our last state.
What if we create one more form field to introduce and display Last Name?
Nice! We’ve abstracted the handleFormChange
method to be able to handle all the input fields and setState
.
What if we add a toggle button to mark the data as valid or invalid and a counter to know how many changes we’ve done to the state?
Yeah! We are rocking! We’ve abstracted a lot of stuff!
Hmmm… Let’s say I do not want a checkbox to control the isValid
variable but a simple toggle button.
Let’s also separate the counter handler from this method. It works well, but in more complex situations where React needs to batch/group changes, it’s not a good policy to rely on the this.state.counter
variable to add one more. This value can change without you being aware of it.
We’re using a shallow copy of it at the instant the operation is invoked, and at that certain point in time you don’t know if its value is the one you were expecting or not!
Let’s go a little bit functional!
Okay — We’ve lost abstraction because we’ve separated the handlers, but it’s for a good reason!
So at this time we keep the handleFormChange
passing an object to the setState
API method. But the handleCounter
and handleIsValid
methods are now functional and start by grabbing the current state and then, depending on that state, changing it to the next one.
This is the correct way of changing the state
of variables that depend on the previous state.
What if we want to console.log()
state changes of the firstName
and lastName
input forms each time a change occurs? Let’s give it a try!
Nice! Each time the handleFormChange
occurs (which means a new key press happened) the logFields()
method is invoked and logs the current state into the console!
Let’s check the browser console:
Wait! What happened here folks? The console log is one change before the current form input! Why is this happening?
setState is async!!
We already knew this but now we’re seeing it with our eyes! What’s happening there? Let’s take a look at the handleFormChange
and logFields
methods above.
So the handleFormChange
method receives the event name and value, then does a setState
of this data. Then it calls the handleCounter
to update the counter info, and in the end invokes the logFields
method. The logFields
method grabs the currentState
and returns ‘Eduard’ instead of ‘Eduardo’.
The thing is: setState
is async and doesn’t act in the moment. React is doing its job and executes the logFields
method first, leaving setState
for the next event loop.
But how can we avoid this kind of situation?
Well, the setState
API has a callback
to avoid this situation:
If we want the logFields()
to take into account the recent changes we’ve made to the state, we need to invoke it inside the callback, like this:
Okay, now it’s working!
We’re telling React: “Hey React! Beware that when you invoke the logFields
method I want you to have the state
already updated okay? I trust you!”
React says: “Okay Edo! I’m going to handle all this batch of stuff I usually do in the backyard with the setState
thingy and only when I’m finished with that I’ll invoke logFields()
! Cool man! Relax!”
And as a matter of fact — it worked!
Okay everyone! By this time we’ve handled the major pitfalls of setState
.
Do you have the courage to go beyond the wall? Grab a cup of coffee and let’s get really kewl…
Getting Fancy with setState( )
Now that we have handleCounter
and handleIsValid
methods, and setState()
expressed with functions, we can compose the state update with other functions! Me likez composition! Let’s have some fun!
We can take the logic inside setState
to a function outside the class component. Let’s call it toggleIsValid
. ☝️
Now this function can live outside the class component, anywhere in your app.
What if we use a higher order function?
Wow! Now we’re not invoking the toggleIsValid
function anymore. We’re invoking an abstract higher order function called toggleKey
and passing a key (string in this case) into it.
How do we need to change the toggleIsValid
function now?
What?! Now we have a function called toggleKey
that receives a key
and returns a new function which changes state according to the supplied key.
This toggleKey
can be in a library or in a helper file. It can be invoked in a lot of different contexts to change the state of whatever you want to its opposite.
Great!
Let’s do the same with the increment counter handler:
Yeah! It works! So nice. Let’s get crazy now…
Shooting the Moon and Getting Back
What if we create a generic makeUpdater
function that receives the transformation function you want to apply, takes the key, and returns the state function managing the state with the transformation function and the key? Little bit confused? Let’s go!
Ok that’s enough…Let’s stop here. ?
You can check all the code we’ve done in this GitHub repo.
Last But Not Least
Don’t forget to avoid the max using state and respect React rendering props cascade.
Don’t forget setState
is async.
Don’t forget setState
can take an object or a function
Don’t forget that you should pass a function in when your next state depends on your previous state.
Bibliography
- React Documentation
- Reach Tech Courses by Ryan Florence, which I really recommend.
Thank you very much!