There are some major new features and enhancements coming up for the React framework; exciting for us developers, rewarding for the end user.
In this article we will be exploring the usage of lazy() and <Suspense />, but for these features to be fully beneficial we also need to understand concurrent React and the benefits of its support for multi threading.
Let’s start with what these terms mean:
As mentioned above, lazy() allows us to perform code splitting within our app and import components very easily.
(Code splitting — The idea of breaking (or splitting) your app into various parts so your entire app does not need to load straight away. You may want to only download the homepage for example, and later on download another part of your app when the user actually visits there. Or, since React is now asynchronous, you may want to load the content right before the user visits there!)
Let’s say I want to load a <Product /> interface, responsible for displaying a product page. This is not necessary when a user first visits my site, therefore I want to split this away from my main bundle.
Let’s go ahead and import this component:
Now we have a simple API for importing components via code splitting, we need a way to display them if they are fully loaded — and display a placeholder if they are not. This is where <Suspense /> comes into play.
Implementing Suspense is also straight forward, and does not require a big change to your JSX markup.
Import Suspense from the standard React package library with the following
Here, we have wrapped our lazily loaded <Product /> within <Suspense>.
Suspense’s fallback prop allows us to pass a component (or any JSX markup) into Suspense to display our loading state. In the example above I have just used a <h2> subheading, but feel free to use your a spinner.
I have deliberately placed some other JSX markup within Suspense to demonstrate that Suspense does not need to be the direct parent object of our lazily loaded components.
This is also advantageous to us as we may not want to display the “Take a look at my product:” text before <Product /> has loaded. Furthermore, <section>provides additional padding and styling that would look odd in an unloaded product state.
If my component has not loaded, Suspense works from that component, up the component tree until a Suspense object is found.
What we can also set up is a scenario where Suspense is waiting for mutliple lazily loaded components to resolve:
In this scenario, all my products need to load before Suspense moves from the fallback to my loaded content.
What is not well documented online is Error Boundaries. What are Error Boundaries?
Error Boundaries allow us to catch errors from anywhere in their component tree, log the error, and display fallback UI. We define ErrorBoundaries as a separate class component, define the magic methods getDerivedStateFromError, componentDidCatch and render, and simply import the component and include it in our render method.
In the case dynamic imports fail to load, Error Boundaries come in handy to display a more friendly UI (more-so than a never ending spinner). An Error Boundary could catch the network error triggered when the module fails to load, which in turn can render a UI that notifies the end user, and could provide a button to reload the resource (or trigger an automatic reload).
More information on Error Boundaries can be found on reactjs.org:
What we talked about above can be implemented in synchronous React, but what if we wanted to take advantage of the asynchronous capabilities of concurrent React?
That is all that needs to be changed to enable Concurrent React.
To continue the talk about Suspense, one of the things it can do in the Concurrent environment is support loading spinner thresholds.
There is more to the story of Suspense. As mentioned above, we can also define a fallback to prevent the loading state displaying, in the event of a fast internet speed. Just include the <Suspense /> maxDuration prop, which takes a millisecond value.
Again, don’t expect this to work in non-concurrent React.
Suspense’s maxDuration threshold is just one advantage of using Concurrent React.
Concurrent React promises to be high performance and deliver smooth UX, which primarily entails less transitions when loading or fetching content. All this functionality is opt-in, meaning developers do not have to adopt any of it, and adopting it will not break your current codebase.
With lazy() and <Suspense />, we are only scratching the surface of what will materialise from an asynchronous React framework — we can certainly expect big changes to packages, apps and user experiences in the coming months.
The React core devs are also working on experimental packages such as react-cache, which has the ability to cache imported and fetched data, and therefore have no need to request it again upon every page visit or refresh. This is not available for public testing at this time, but may be another valuable tool for additional UI simplicity for the end user.