---
id: concurrent-mode-patterns
title: Concurrent UI Patterns (Experimental)
permalink: docs/concurrent-mode-patterns.html
prev: concurrent-mode-suspense.html
next: concurrent-mode-adoption.html
---
>Caution:
>
>This page describes **experimental features that are [not yet available](/docs/concurrent-mode-adoption.html) in a stable release**. Don't rely on experimental builds of React in production apps. These features may change significantly and without a warning before they become a part of React.
>
>This documentation is aimed at early adopters and people who are curious. If you're new to React, don't worry about these features -- you don't need to learn them right now.
Usually, when we update the state, we expect to see changes on the screen immediately. This makes sense because we want to keep our app responsive to user input. However, there are cases where we might prefer to **defer an update from appearing on the screen**.
For example, if we switch from one page to another, and none of the code or data for the next screen has loaded yet, it might be frustrating to immediately see a blank page with a loading indicator. We might prefer to stay longer on the previous screen. Implementing this pattern has historically been difficult in React. Concurrent Mode offers a new set of tools to do that.
- [Transitions](#transitions)
- [Wrapping setState in a Transition](#wrapping-setstate-in-a-transition)
- [Adding a Pending Indicator](#adding-a-pending-indicator)
- [Reviewing the Changes](#reviewing-the-changes)
- [Where Does the Update Happen?](#where-does-the-update-happen)
- [Transitions Are Everywhere](#transitions-are-everywhere)
- [Baking Transitions Into the Design System](#baking-transitions-into-the-design-system)
- [The Three Steps](#the-three-steps)
- [Default: Receded → Skeleton → Complete](#default-receded-skeleton-complete)
- [Preferred: Pending → Skeleton → Complete](#preferred-pending-skeleton-complete)
- [Wrap Lazy Features in ``](#wrap-lazy-features-in-suspense)
- [Suspense Reveal “Train”](#suspense-reveal-train)
- [Delaying a Pending Indicator](#delaying-a-pending-indicator)
- [Recap](#recap)
- [Other Patterns](#other-patterns)
- [Splitting High and Low Priority State](#splitting-high-and-low-priority-state)
- [Deferring a Value](#deferring-a-value)
- [SuspenseList](#suspenselist)
- [Next Steps](#next-steps)
## Transitions {#transitions}
Let's revisit [this demo](https://codesandbox.io/s/infallible-feather-xjtbu) from the previous page about [Suspense for Data Fetching](/docs/concurrent-mode-suspense.html).
When we click the "Next" button to switch the active profile, the existing page data immediately disappears, and we see the loading indicator for the whole page again. We can call this an "undesirable" loading state. **It would be nice if we could "skip" it and wait for some content to load before transitioning to the new screen.**
React offers a new built-in `useTransition()` Hook to help with this.
We can use it in three steps.
First, we'll make sure that we're actually using Concurrent Mode. We'll talk more about [adopting Concurrent Mode](/docs/concurrent-mode-adoption.html) later, but for now it's sufficient to know that we need to use `ReactDOM.createRoot()` rather than `ReactDOM.render()` for this feature to work:
```js
const rootElement = document.getElementById("root");
// Opt into Concurrent Mode
ReactDOM.createRoot(rootElement).render();
```
Next, we'll add an import for the `useTransition` Hook from React:
```js
import React, { useState, useTransition, Suspense } from "react";
```
Finally, we'll use it inside the `App` component:
```js{3-5}
function App() {
const [resource, setResource] = useState(initialResource);
const [startTransition, isPending] = useTransition({
timeoutMs: 3000
});
// ...
```
**By itself, this code doesn't do anything yet.** We will need to use this Hook's return values to set up our state transition. There are two values returned from `useTransition`:
* `startTransition` is a function. We'll use it to tell React *which* state update we want to defer.
* `isPending` is a boolean. It's React telling us whether that transition is ongoing at the moment.
We will use them right below.
Note we passed a configuration object to `useTransition`. Its `timeoutMs` property specifies **how long we're willing to wait for the transition to finish**. By passing `{timeoutMs: 3000}`, we say "If the next profile takes more than 3 seconds to load, show the big spinner -- but before that timeout it's okay to keep showing the previous screen".
### Wrapping setState in a Transition {#wrapping-setstate-in-a-transition}
Our "Next" button click handler sets the state that switches the current profile in the state:
```js{4}
);
}
```
**[Try it on CodeSandbox](https://codesandbox.io/s/brave-villani-ypxvf)**
Notice how when you type into the input, the `` component suspends, and we see the `
Loading...
` fallback until we get fresh results. This is not ideal. It would be better if we could see the *previous* translation for a bit while we're fetching the next one.
In fact, if we open the console, we'll see a warning:
```
Warning: App triggered a user-blocking update that suspended.
The fix is to split the update into multiple parts: a user-blocking update to provide immediate feedback, and another update that triggers the bulk of the changes.
Refer to the documentation for useTransition to learn how to implement this pattern.
```
As we mentioned earlier, if some state update causes a component to suspend, that state update should be wrapped in a transition. Let's add `useTransition` to our component:
```js{4-6,10,13}
function App() {
const [query, setQuery] = useState(initialQuery);
const [resource, setResource] = useState(initialResource);
const [startTransition, isPending] = useTransition({
timeoutMs: 5000
});
function handleChange(e) {
const value = e.target.value;
startTransition(() => {
setQuery(value);
setResource(fetchTranslation(value));
});
}
// ...
}
```
**[Try it on CodeSandbox](https://codesandbox.io/s/zen-keldysh-rifos)**
Try typing into the input now. Something's wrong! The input is updating very slowly.
We've fixed the first problem (suspending outside of a transition). But now because of the transition, our state doesn't update immediately, and it can't "drive" a controlled input!
The answer to this problem **is to split the state in two parts:** a "high priority" part that updates immediately, and a "low priority" part that may wait for a transition.
In our example, we already have two state variables. The input text is in `query`, and we read the translation from `resource`. We want changes to the `query` state to happen immediately, but changes to the `resource` (i.e. fetching a new translation) should trigger a transition.
So the correct fix is to put `setQuery` (which doesn't suspend) *outside* the transition, but `setResource` (which will suspend) *inside* of it.
```js{4,5}
function handleChange(e) {
const value = e.target.value;
// Outside the transition (urgent)
setQuery(value);
startTransition(() => {
// Inside the transition (may be delayed)
setResource(fetchTranslation(value));
});
}
```
**[Try it on CodeSandbox](https://codesandbox.io/s/lively-smoke-fdf93)**
With this change, it works as expected. We can type into the input immediately, and the translation later "catches up" to what we have typed.
### Deferring a Value {#deferring-a-value}
By default, React always renders a consistent UI. Consider code like this:
```js
<>
>
```
React guarantees that whenever we look at these components on the screen, they will reflect data from the same `user`. If a different `user` is passed down because of a state update, you would see them changing together. You can't ever record a screen and find a frame where they would show values from different `user`s. (If you ever run into a case like this, file a bug!)
This makes sense in the vast majority of situations. Inconsistent UI is confusing and can mislead users. (For example, it would be terrible if a messager's Send button and the conversation picker pane "disagreed" about which thread is currently selected.)
However, sometimes it might be helpful to intentionally introduce an inconsistency. We could do it manually by "splitting" the state like above, but React also offers a built-in Hook for this:
```js
import { useDeferredValue } from 'react';
const deferredValue = useDeferredValue(value, {
timeoutMs: 5000
});
```
To demonstrate this feature, we'll use [the profile switcher example](https://codesandbox.io/s/musing-ramanujan-bgw2o). Click the "Next" button and notice how it takes 1 second to do a transition.
Let's say that fetching the user details is very fast and only takes 300 milliseconds. Currently, we're waiting a whole second because we need both user details and posts to display a consistent profile page. But what if we want to show the details faster?
If we're willing to sacrifice consistency, we could **pass potentially stale data to the components that delay our transition**. That's what `useDeferredValue()` lets us do:
```js{2-4,10,11,21}
function ProfilePage({ resource }) {
const deferredResource = useDeferredValue(resource, {
timeoutMs: 1000
});
return (
Loading profile...}>
Loading posts...}>
);
}
function ProfileTimeline({ isStale, resource }) {
const posts = resource.posts.read();
return (
{posts.map(post => (
{post.text}
))}
);
}
```
**[Try it on CodeSandbox](https://codesandbox.io/s/vigorous-keller-3ed2b)**
The tradeoff we're making here is that `` will be inconsistent with other components and potentially show an older item. Click "Next" a few times, and you'll notice it. But thanks to that, we were able to cut down the transition time from 1000ms to 300ms.
Whether or not it's an appropriate tradeoff depends on the situation. But it's a handy tool, especially when the content doesn't change very visible between items, and the user might not even realize they were looking at a stale version for a second.
It's worth noting that `useDeferredValue` is not *only* useful for data fetching. It also helps when an expensive component tree causes an interaction (e.g. typing in an input) to be sluggish. Just like we can "defer" a value that takes too long to fetch (and show its old value despite others components updating), we can do this with trees that take too long to render.
For example, consider a filterable list like this:
```js
function App() {
const [text, setText] = useState("hello");
function handleChange(e) {
setText(e.target.value);
}
return (
...
);
}
```
**[Try it on CodeSandbox](https://codesandbox.io/s/pensive-shirley-wkp46)**
In this example, **every item in `` has an artificial slowdown -- each of them blocks the thread for a few milliseconds**. We'd never do this in a real app, but this helps us simulate what can happen in a deep component tree with no single obvious place to optimize.
We can see how typing in the input causes stutter. Now let's add `useDeferredValue`:
```js{3-5,18}
function App() {
const [text, setText] = useState("hello");
const deferredText = useDeferredValue(text, {
timeoutMs: 5000
});
function handleChange(e) {
setText(e.target.value);
}
return (
...
);
}
```
**[Try it on CodeSandbox](https://codesandbox.io/s/infallible-dewdney-9fkv9)**
Now typing has a lot less stutter -- although we pay for this by showing the results with a lag.
How is this different from debouncing? Our example has a fixed artificial delay (3ms for every one of 80 items), so there is always a delay, no matter how fast our computer is. However, the `useDeferredValue` value only "lags behind" if the rendering takes a while. There is no minimal lag imposed by React. With a more realistic workload, you can expect the lag to adjust to the user’s device. On fast machines, the lag would be smaller or non-existent, and on slow machines, it would be more noticeable. In both cases, the app would remain responsive. That’s the advantage of this mechanism over debouncing or throttling, which always impose a minimal delay and can't avoid blocking the thread while rendering.
Even though there is an improvement in responsiveness, this example isn't as compelling yet because Concurrent Mode is missing some crucial optimizations for this use case. Still, it is interesting to see that features like `useDeferredValue` (or `useTransition`) are useful regardless of whether we're waiting for network or for computational work to finish.
### SuspenseList {#suspenselist}
`` is the last pattern that's related to orchestracting loading states.
Consider this example:
```js{5-10}
function ProfilePage({ resource }) {
return (
<>
Loading posts...}>
Loading fun facts...}>
>
);
}
```
**[Try it on CodeSandbox](https://codesandbox.io/s/proud-tree-exg5t)**
The API call duration in this example is randomized. If you keep refreshing it, you will notice that sometimes the posts arrive first, and sometimes the "fun facts" arrive first.
This presents a problem. If the response for fun facts arrives first, we'll see the fun facts below the `
Loading posts...
` fallback for posts. We might start reading them, but then the *posts* response will come back, and shift all the facts down. This is jarring.
One way we could fix it is by putting them both in a single boundary:
```js
Loading posts and fun facts...}>
```
**[Try it on CodeSandbox](https://codesandbox.io/s/currying-violet-5jsiy)**
The problem with this is that now we *always* wait for both of them to be fetched. However, if it's the *posts* that came back first, there's no reason to delay showing them. When fun facts load later, they won't shift the layout because they're already below the posts.
Other approaches to this, such as composing Promises in a special way, are increasingly difficult to pull off when the loading states are located in different components down the tree.
To solve this, we will import `SuspenseList`:
```js
import { SuspenseList } from 'react';
```
`` coordinates the "reveal order" of the closest `` nodes below it:
```js{3,11}
function ProfilePage({ resource }) {
return (
Loading posts...}>
Loading fun facts...}>
);
}
```
**[Try it on CodeSandbox](https://codesandbox.io/s/black-wind-byilt)**
The `revealOrder="forwards"` option means that the closest `` nodes inside this list **will only "reveal" their content in the order they appear in the tree -- even if the data for them arrives in a different order**. `` has other interesting modes: try changing `"forwards"` to `"backwards"` or `"together"` and see what happens.
You can control how many loading states are visible at once with the `tail` prop. If we specify `tail="collapsed"`, we'll see *at most one* fallback at the time. You can play with it [here](https://codesandbox.io/s/adoring-almeida-1zzjh).
Keep in mind that `` is composable, like anything in React. For example, you can create a grid by putting several `` rows inside a `` table.
## Next Steps {#next-steps}
Concurrent Mode offers a powerful UI programming model and a set of new composable primitives to help you orchestrate delightful user experiences.
It's a result of several years of research and development, but it's not finished. In the section on [adopting Concurrent Mode](/docs/concurrent-mode-adoption.html), we'll describe how you can try it and what you can expect.