Creating better user experiences with React 18 Suspense and Transitions
Tags: react, tutorial, frontend, webdev
Published: 2022-01-01 17:13:40

---
title: Creating better user experiences with React 18 Suspense and Transitions
published: true
description: In this tutorial we reimplement the awesome Solid Suspense Transitions demo with React 18 Suspense and Transitions (useTransition).
tags: react, tutorial, frontend, webdev
//cover_image: https://direct_url_to_image.jpg
---

## What are you talking about?

[React 18](https://reactjs.org/blog/2021/06/08/the-plan-for-react-18.html) is [around the corner](https://github.com/reactwg/react-18/discussions/112) and one of the big things coming along with it is [_Concurrent Mode_](https://reactjs.org/docs/concurrent-mode-intro.html). The general idea is that it's going to allow for concurrent / interruptible rendering, which in turn will enable us to create more responsive and enjoyable applications for our users.

This post will focus on two Concurrent Mode features in particular, [Suspense for Data Fetching](https://reactjs.org/docs/concurrent-mode-suspense.html) and [Transitions](https://reactjs.org/docs/concurrent-mode-patterns.html#transitions), which will allow us to create much better loading experiences (and let's face it: we desperately need it!).

Up until now, when needing to fetch data before showing some UI that depends on that data, we typically rendered a loading state in its place, for example a loading spinner or skeleton, until the request resolved with the necessary data.

As an example, let's look at the following CodeSandbox:

{% codesandbox v3kyj %}

Every time we change tabs, the `Content` component for said tab fetches some data. While that data is being fetched, we render a little loading component in the content's place. This isn't the worst experience and indeed it's more-or-less the standard way we see loading states implemented in apps today.

Wouldn't it be nicer though if we didn't show that in-between loading state at all? What if, instead, we held on to the previous state of the UI until the data was ready? To be fair, we can technically achieve this with React 17 if we really want to but it's definitely a challenge to get right and not very straight-forward. React 18, on the other hand, makes this very simple:

{% codesandbox t8yt0 %}

Now instead of switching tabs immediately we stay on the tab we're on and continue to show its content until the new tab's content is ready. We effectively have taken complete control over how we want our loading states to behave. The result is a more seamless and less jarring experience for the user.

This is now a good time to point out that the demo above is a rewrite of the awesome [SolidJS](https://www.solidjs.com/) demo showcasing its [implementation of Suspense and Transitions](https://www.solidjs.com/examples/suspensetabs), which its had for a while now. In general SolidJS and its community is incredible and I highly recommend folks check it out.

If you're a "just show me the code" type of person then that's it! Fork the demo and make it yours! If you want a bit more of an explanation though, continue on!

## How does it work?

The magic in this demo, as hinted at in the introduction, lies in the use of Suspense for data fetching and the new `useTransition` hook.

### Setup

First though, in order to enable any of these features, we need to make a small change to how we render our root. Instead of rendering via `ReactDOM.render`, we use the new `ReactDOM.createRoot`:

```jsx
import ReactDOM from "react-dom";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
```

And just like that we have access to Concurrent Mode! 

### Suspense (for data fetching)

Now that we're up and running with the new features, we can examine in more details our use of `Suspense`:

```jsx
<Suspense fallback={<Loader />}>
  {tab === 0 && <Content page="Uno" resource={resource} />}
  {tab === 1 && <Content page="Dos" resource={resource} />}
  {tab === 2 && <Content page="Tres" resource={resource} />}
</Suspense>
```

Up until now, we've typically used `Suspense` when lazy loading components. However in this case our components aren't lazy loaded at all! Instead of suspending on the async loading of the component itself, we're now suspending on the async loading of data within it.

Checking within `Content`, we see a peculiarly simple component:

```jsx
function Content({ page, resource }) {
  const time = resource.delay.read();

  return (
    <div className="tab-content">
      This content is for page "{page}" after {time.toFixed()}
      ms.
      <p>{CONTENT[page]}</p>
    </div>
  );
}
```

Normally we would expect to see a check for `time`, which would probably be set in state, for example maybe something like:

```jsx
const [time, setTime] = useState();

useEffect(() => {
  resource.then((data) => {
    setTime(data)
  })
}, [])

return time &&
  (<div className="tab-content">
      This content is for page "{page}" after {time.toFixed()}
      ms.
      <p>{CONTENT[page]}</p>
    </div>
  );
```

However, instead we see the `jsx` being unconditionally returned. Further `time` isn't set in state to trigger a rerender, rather its set to `resource.delay.read()`. And that's the clue to how this is all working!

You'll see when looking into our `fakeAPI` file, that `resource.delay` is actually a special kind of promise, which in our naive implementation taken from the official React examples, is essentially a simplified mock of what something a React 18 compatible data fetching library would provide (and what [Relay](https://relay.dev/) already does provide!).

The API itself is an implementation detail, the main take-away is that in React 18, `Suspense` wrapped components will be able to continuously check if the async data a component is attempting to read has been resolved, throwing and continuing to render the fallback until it's ready.

### Transitions

With this new use of `Suspense`, implementing components that depend on async data is much more straight-forward. By itself though, we still can't easily control our loading states. We need the other major piece of our puzzle: the new and shiny `useTransition` hook.

Note that this hook is really all about defining some state changes as transitional rather than urgent, meaning that if some new work needs to be done during rendering of those changes, React should interrupt the rendering and perform that new work first. For a great in depth example of how this can be used to improve UX, check out [this guide](https://github.com/reactwg/react-18/discussions/65) from core React team member [Ricky Hanlon](https://twitter.com/rickhanlonii).

In our case, we're going to use `useTransition` to tell React that setting the new tab and setting the new resource (which in turn fetches the tab's data) are both transitional state changes and as such we want it to hold off on rendering the resulting UI.

This is accomplished by wrapping both of our transitional state changes in a call to `startTransition`, which we get from `useTransition`:

```jsx
const [isPending, startTransition] = useTransition();

function handleClick(index) {
  startTransition(() => {
    setTab(index);
    setResource(fetchData());
  });
}
```

You will also notice that along with `startTransition` we get another utility: `isPending`. As you can probably guess, this returns `true` while our transitional changes are still ongoing. This can be used to show an extra piece of loading state so the user knows something is happening in the background. 

In our example, that's the "loading bar" at the top, along with some styling changes to the tabs and the content:

```jsx
<GlobalLoader isLoading={isPending} />
// ...
<div className={`tab ${isPending ? "pending" : null}`}>
// ...
```

And that's really it! Once you get past the theory and jargon, the practical implementation is very straight-forward. It basically comes down to just wrapping transitional changes with `startTransition` and handling other UX details with `isPending` 🙌

## That's all folks

If you can't tell, I'm super excited for React 18 and Concurrent Mode. Along with streaming server rendering, this release is going to be a complete game changer as far as React goes. I can't wait to use it in "the real world" to make applications more snappy and users more happy!

Hope you got something out of this as always questions / comments are more than welcome! 🤙