Published on

Use startTransition for Concurrent Rendering in React


A lot of the focus with the release of React 18 has been on performance improvements, and that's good news as in-browser single page React applications continue to inch closer to the level of responsiveness that is possible with apps that run on the desktop. One such improvement is the addition of the startTransition API that is available with React 18.

In short, the objective of the startTransition interface is to keep apps responsive during expensive screen updates by moving non-urgent updates to the background. UI updates fall into two categories:

  1. Urgent updates: consists of immediate interaction events like click, key down, change, mouse move, etc. A transition is considered urgent when even the smallest delay would cause an inconsistency with the way we instinctively feel an object should behave.
  2. Transition updates: consist of updates to the UI that move from one view to another, usually resulting from a change in state. With non-urgent transitions, a small delay will not cause dissonance between the behavior of the UI and what is expected.

The distinct difference between urgent and non-urgent transitions is that an urgent transition that does not behave intuitively is disruptive to the usability of the application resulting in frustration. Prior to React 18, all updates were considered urgent. In this model, where all updates were treated equally, creating responsive computationally intensive interfaces like real-time data dashboards were incredibly difficult. To make it work, you'd have to either try deferring updates using setTimeout or use throttling and debouncing to deal with an influx of updates.

If you used setTimeout, you'd still have an influx of scheduled updates which would still have to render when the timeout fires even if all but the most recent update were no longer relevant. If you debounce the updates, you might be delaying the update for longer than it would take to render the update to begin with. Throttling might be a better solution, but with both throttling and debouncing you have this situation where massive chunks of work get stacked up and cause the UI to become briefly unresponsive. This case study does a really good job of demonstrating this with a real world example.

With the startTransition API, you have the ability to explicitly mark certain updates as non-urgent transitions, effectively making these updates secondary in priority. Updates wrapped with startTransition are considered non-urgent and can be interrupted if something more important comes along, such as a click or key press. When a transition update is interrupted, React will toss the stale renders currently in the queue and render only the most recent update. This is especially beneficial for rendering computationally expensive UIs on slower CPUs because the browser doesn't get bogged down with rendering irrelevant content.

As I previously mentioned, all updates were considered urgent prior to version 18. Its important to note that React 18 continues with this behavior unless an update is wrapped with startTransition.

The useTransition hook also offers an isPending flag, which allows you to show a some form of feedback loading feedback like temporarily blurring or fadding that part of the UI or showing a loading spinner while the user waits.

The bottom line here is that if you're building a heavy, computationally expensive UI where updates can stack up and turn stale, you're going to want to make heavy use of the startTransition API.

Further reading: