React Async State Updates

Jul 11th, 2022

Background

The motivation for writing this blog was from a blocker that I encountered during a take home project. It is intended to illustrate a buggy attempt that was made along with a working solution. Even though this example is specific to implementing a certain task, the underlying concept of how state updates are made is fundamental to uunderstanding React under the hood.

Overview

The make believe example we will be covering is to build a form that takes an input of student names and return the submitted student list sorted in alphabetical order. Since this post will focus on how states are updated in React, the basics for how to set up a form will not be covered.

Storing Values

The student names are stored and updated within the useState hook. values is the variable that will store each new input and setValues will update values every time it is called. We initially set values to an empty object.

  const [values, setValues] = useState({});

The submitted student list is stored and updated within another useState hook. submissions is the variable that will store all submissions that are added when setSubmissions is called. We initially set submissions to mock data(for testing the behavior).

  const [submissions, setSubmissions] = useState([{ name: 'John' }]);

Sorting Submissions

The handleSortName function is required to sort all of the names stored in submissions. It is important to note that sort() will mutate the array which it is applied to, therefore a new array is created for the sorted submissions. localeCompare() will perform the sorting between each individual name and sort them in alphabetical order.

  const handleSortName = (submissions) => {
    return [...submissions].sort((a, b) => a.name.localeCompare(b.name));
  };

How React Update State

Now back to the crux of this blog. When multiple updates are made to a component the latest changes may not be reflected on the DOM after the component renders. This is due to the fact that React state updates are asynchronous. This section of React's documentation and this entry on a very popular blog goes into more details.

With regards to our example the multiple operations performed are:

  1. submitting a student name
  2. sorting the list of submitted name

First Attempt

Initially, I attempted to apply a side-effect(sorting in our case) to submissions with the useEffect hook. This setup will execute the handleSortName callback whenever there is a change to submissions.

  useEffect(() => {
    handleSortName(submissions);
  }, [submissions]);

First Attempt Behavior

As we can see the sorting behavior is one step behind and will not sort the most recent submission. Strangely the submitted student list will be sorted when a change is detected within the input value. This is not the expected behavior and there is a timing issue somewhere. 🤔

Solution

Instead of listening for changes and applying a side effect, we will execute handleSortName before mapping through the submissions and rendering the <Submission /> component. This works because handleSortName is executed before <Submission /> is rendered.

...
{ handleSortName(submissions).map((submission, index) => (
  <Submission key={index} submission={submission} />
))}
...

Executing handleSortName will then cause <Submission /> to re-render because there has been a change in its state. Finally the latest changes will be reflected on the DOM. The entire codebase with working solution can be found here.

In the context of using the latest values, another solution that can be applied are functional updates.

Solution Behavior

When a new student name is entered, the submitted student list will automatically be sorted as expected. 🙌

Recap

I hope by now you have a better understanding of how states are updated in React. The main takeaway is that they are batched together for performance and therefore asynchronous. Since React will re-render a component every time state(or props) changes, a common solution would be to update the state again in order for changes to be reflected on the DOM.