React setState & its Async Nature

Jeanette Abell
Geek Culture
Published in
5 min readMay 27, 2021

Do you recall what the second optional argument to setState is in React? What is its purpose? If you’re like me, this second argument didn’t make it into local storage on my first learning go round. After building more with React, it has become clear that this second argument actually deserves a little time in the spotlight.

A high level understanding of React components as a tree with top-down cascading data flow of props and state is enough for any new developer to get busy. Some unexpected outcomes may pop up as you continue to build that might make you scratch your head and consider more of the nuance taking place.

One such ‘gotcha’ has to do with the setState function and the pickle of accidentally utilizing a version of state in your logic that is different from the one you intended to use. The initial takeaways regarding setting and updating state in React class components may get compiled to consciousness as the following two main ideas:

  1. I can directly set state only in the constructor (by declaring this.state equal to a POJO, to which I can supply keys and values that now make up said state).
  2. Anytime I would like to update the state thereafter, I need to use setState (supplying changes in a similar fashion to the constructor — specifying which piece(s) of state I would like to update with some corresponding updated value).

In this basic snippet, state is directly set on lines 6–8 in the constructor, and will get updated via the function on lines 13–15 that executes on a click, and invokes setState with the intended next state. While these two main ideas are the basis of setting and updating local state within a component, a third point — that setState is acting asynchronously — is also of utmost importance to understand early. The version of state the UI displays at a given moment is actually determined asynchronously — and this nature of ‘not occurring at precisely that instant’ should be accounted for by the developer.

As expressed in the React documentation:

“When you use React, at a single point in time you can think of the render() function as creating a tree of React elements. On the next state or props update, that render() function will return a different tree of React elements. React then needs to figure out how to efficiently update the UI to match the most recent tree.”

So what happens when you call setState? As soon as setState is called in React, a merge of the object you passed to setState into the current state of the component occurs. This kicks off a process called reconciliation. Without getting too deep into this diffing algorithm here, the big picture takeaway is that while React is reconciling those differences in the two trees, even the lightning speed ( O(n) ) of this action can be too slow for the developer’s “next-state” dependent outcome that they have coded on the very next line to take effect and actually produce the result they expected.

If we were to insert a console.log(this.state.isBasic) after the setState call on line 16 in the previous snippet, what is going to be logged to console is likely to be the previous state as opposed to the next one. The UI is going to change mad fast and toggle the button’s text accordingly to reflect the next state. However, that console.log statement is going to happen even faster, and print prior to that state update, logging to console the opposite state than what you are going to witness rendering before your eyes. (See below).

Logic executed right after a setState call may lead to unpredictable results.

Imagine a car using the left lane of a single lane highway to pass a car just a few feet in front of them. The event aspires to unfold in the following way: The passing vehicle gets over enough for safe clearance (task one complete), and then the vehicle speeds up significantly to pass (task two). The driver would not want to really step on the gas until the first clearance and safety task has been carried out. Otherwise, the acceleration of task two is going to deliver you a very different outcome than was intended — aka an accident! We want to avoid these types of timing collisions in our updating process within React so that we don’t end up with these accidents — aka bugs!

Let us officially amend a third important takeaway regarding setState:

3There is an optional second argument that expects a callback function which will only execute after the state updating merge of the first argument has completed.

Continuing on with our basic example, if you were to log the state through an arrow function as the second argument to setState, you will always see the updated state reflected in your console. (See below).

This console.log will always reflect the state that was just updated by setState.

It is useful to be aware of this option when incorporating asynchronous actions like AJAX requests or redux thunks into code that is meant to be updating state and then using it accordingly. This pitfall can even occur when simply wanting to execute logic in sequence, like calling any old function on the line immediately following a setState call — and expecting it to use an already updated state in said function. Case in point, the already updated state may not be there. Any action meant to take place after an update to state, utilizing that updated state, is best included as the second argument to setState.

This bug can manifest in much more complex ways than were covered in this article. Having this knowledge about setState’s asynchronous, but not promise returning nature in your back pocket can speed up your debugging time once you know what to look for. I encountered this need to enrich my setState understanding while attempting to grab an image off of the DOM in a function call right after setState had been called, expecting that saved image returned by the function following setState to reflect the state update. Placing that logic inside setState was a breezy fix brought to you by that optional second argument.

As always, there are clear examples with concise explanations around the use of setState in the React docs! I hope that the second argument to setState feels seen, and that you never forget about it as an option for your state update logic security!

--

--

Jeanette Abell
Geek Culture

Fullstack Software Engineer, Arts Professional, & Creative Mind