Know the new Concurrent Suspense and Transition API
Suspense feature was released as part of
React 16 version. There, it had only one use case. It was meant to be used with its
React.lazy API for code splitting. It would serve as a fallback when the element was not on yet downloaded and presented. It had one major drawback though. It could not be used on the server-side rendering engine.
It had other caveats however those were only temporary. It was well known that
Suspense was a cornerstone of React’s concurrent engine mode. It was meant as much more than a code-splitting dedicated API.
With the Release of React 18, the
Suspense feature has been further developed and enhanced. It fits many use cases and now it is compatible with
SSR. It still can’t be used for data fetching though. This feature is still in experimental mode and might make it to a further release.
The React team provides a progressive update. Only by using the new
ReactDOM.createRoot API we will unlock concurrency and all the fancy new
Suspense? It is a lower-level engine API that can be used to pause a component execution. How is that done? In a nutshell, it all boils down to a component throwing a Promise that is intercepted by the engine. It will defer the execution of that component tree until that Promise is resolved or rejected.
We can see a bit of the API if we look at React’s source code.
fallback attribute of
Suspense is where we specify the component’s loading behavior. What happens if there are multiple
Suspense wrappers? The fallback from the closest parent in the
JSX tree will be used.
Let’s see an example:
The stable version of suspense found in
17 was the synchronous implementation of the concurrent idea. It was known as
Legacy Suspense, the component tree would be just be hidden from the UI. It won’t be discarded as it should. That would be done by adding the
display: none style to the parent DOM element. This implementation led to inconsistencies in the firing of lifecycle events. Those were fired prior to the ComponentThatSuspends getting resolved. It led to quite a few bugs and undesired behaviors.
Concurrent Suspense version, the component will be discarded when suspended. The uncompleted render trees don’t get committed. Only when the component is ready it will be placed in the DOM. Its layout effects will then be fired. The execution order is therefore much more intuitive. The components are now purely asynchronous.
- The element tree is immediately mounted in the DOM
- Effects/Lifecycle are fired
- The tree is visually hidden when suspense is triggered
- It is made visible only after the ComponentThatSuspends is resolved
- Element is not mounted until the ComponentThatSuspends is resolved
- Effects/Lifecycle are fired
Why did not the React team directly implement the
Concurrent Supense approach? It was because of the legacy Class Components lifecycle. There was no proper way for the
Concurrent Suspense engine to deal with events like
componentWillMount. That is the reason behind the React team prefixing all those with
We have just scratched the surface previously. How would those
layout events be executed in
Concurrent Suspense? Some components can be removed and re-added later to the DOM.
Those will now run now on the
show events. When React needs to hide the suspended nodes it will run their cleanup function. When needing to show the suspended elements it will retrigger their layout effects.
Contrary to what is happening with
Legacy suspense those layout effects may run several times. It is not true anymore that layout effects with
 dependencies will just run once. It is better not to think of those in terms of lifecycle events but as units of behavior for the different React features.
If we look at the below example:
We might get several
log on mount and
log on cleanup log statements. It depends if the component gets suspended and re-added multiple times.
Legacy Suspense would throw an error when used in
SSR. It was inconvenient.
A new HTML concurrent server-rendered has been added. Instead of producing a
string it does output a
stream can be used to push early the initial HTML. It will include the
Suspense fallback placeholders. When the content is ready it will emit an HTML fragment with a
script tag to inject the components in the right place. The React library will be able to hydrate parts of the application while the stream is not completed.
This stream feature will come in really handy when
Concurrent Suspense supports data fetching.
Let’s see the API that makes it all possible:
Let’s see a representation of how this looks in the Browser
The green area placeholders are elements that have been hydrated. The
spinner is a component that is suspended on the server and yet not streamed.
For more information, you can see the fulls details here.
React 18 we get another suspense engine feature:
<SuspenseList />. We can use this component to wrap multiple
<Suspense /> instances. With this feature, we can coordinate and orchestrate how those are revealed to the user. This helps mitigate the unpredictability of the network.
It takes two props:
- revealOrder: it defines the order in which they should be revealed. The options are
- tail: it lets us collapse or hide all the suspense fallbacks. The values are
hidden. By default, it will display all the suspense fallbacks.
Let’s see a usage example:
Notice how in the above example the order of appearance to be
forwards. This will show the components appearing in a sequential order.
The React 18 release comes with some new APIs that can be used to further fine-tune the
We might want to keep loaded components around while the new ones are being fetched. The user would keep on seeing relevant info while the download/parsing of the component is happening behind the scene. For that, we can use the
Let’s look at a concrete example. Let’s imagine we have a set of tabs that the user can navigate around. When switching tabs, it is more relevant to display the old tab content instead of showing the
Suspense fallback for the new content. The page should stay interactive. If the user was to click on another tab the engine will be discarding the current suspense task and load the new one.
Let’s see the example on code. Let’s see the pre-React 18 version:
Let’s use the new
transition API. The
useTransition hook API provides a progress indicator
 and a method to start the less critical transition
The page will start interactive at all times while the processing happens asynchronously.
It is important to give feedback to the user that something is happening. By using the
isPending boolean we can dim the content. The action
pressing a tab has a direct reaction:
content is dimmed.
How is this working? The React engine is keeping the previous version tree of the UI while it waits for the new one to be completed. It is like React is working concurrently in another branch. The will be two versions of the same JSX tree.
With this transition API, we are now more in control of how we want our application to behave. We have more control over its execution. It will all boil down to our specific UX scenario.
We have seen all the cool features from the
Concurrent Suspense API. It is a massive improvement in performance. It also fixes a few bugs related to the
Legacy Suspense implementation. Just the
SSR alone is worth the upgrade to
Although this release is super exciting we just can’t wait for the
Suspense data fetching to be ready. It will be a massive game-changer unlocking many patterns. We have to be patient. The difficulty lies in that there are many moving pieces that need to be synchronized.
It has taken some time to put this release together. However their implementation of
Suspense was worth the wait.
Thanks for reading.