The American dictionary states that suspense means a feeling of excitement while waiting for something uncertain to happen. So, let’s get excited and learn about React.Suspense and how this helps us wait for something uncertain!
React.Suspense is a component that let you suspend, or delay, the component rendering if the components, or soon data, are imported from outside your loaded code. Later this December we may or may not, learn about Suspense for data fetching, but right now we are diving into how React.Suspense is used today with code splitting.
Code splitting
Code splitting is exactly as it sounds: splitting up your code. This might be on a route-level, meaning that each sub route in your application is its own part or chunk. This is a good place to begin, as it will make your split parts more even. The user experience will also be intact as users are already used to a page transition with a new render.
You can also split up your code at a component-level. You try to identify smaller parts of the application which is rarely used or shown to the user.
Optimize all the codes
As your application gets bigger, with code and features, your loading time will increase as well. Especially if you include multiple third-party libraries. The user today, is expecting fast applications with minimum loading time. Some even say that 53% of mobile users abandons sites that loads longer than 3 seconds. This is where code splitting comes to into play.
When you have divided your application code into chunks, you can reduce the initial loading time of your application dramatically by only loading the chunk necessary for the initial render. When the user navigates in your application the rest of the chunks can be loaded as needed.
Code splitting with Suspense
Code splitting is a feature supported by your bundler, like Webpack or Parcel. However, your code facilitates the actual import and use of these chunks. In React 16.6, Suspense for code splitting was released, and it makes the code splitting really easy.
Suspense handles the loading state and lets you delay the rendering of parts of the application tree. While the chunks are loading suspense shows a fallback component until the chunk is ready to render. This means that you do not have to have a local state checking if a component is loading or not and you do not have to clutter your render function with an if-statement checking this state. Suspense deals with all of this for you!
The actual import of these chunks is done by React.Lazy and dynamic imports. Let’s take a closer look how this is done:
const WishList = React.lazy(() => import('./WishList')) const GiftTable = React.lazy(() => import('./GiftTable')) <Suspense fallback={<h1>We are loading...🎅</h1>}> <WishList /> <GiftTable /> </Suspense>
The example shows a chunk named WishList
and a chunk named GiftTable
. Instead of a regular import statement we use React.lazy. React.lazy takes a function, that must call a dynamic import, as an argument. This returns a Promise which resolves to a module exporting a React component. If you want to know more about React.lazy, hang on a few more days for an article just about lazy loading in React!
Because WishList
and GiftTable
is dynamically imported, we wrap the components in Suspense. Suspense will try to render WishList
and, of course GiftTable
. If the chunks is not loaded completely, the Suspense component will render the fallback component until it can render the two child components successfully.
For simplicity, in this example the suspense component is an immediate parent of WishList and GiftTable. However, suspense can be placed anywhere above them in the tree, depending on how you want your loading state to be.
But, what if it fails?!
As earlier stated, Suspense only handles the loading state as you try to dynamically import a component, not errors that may occur. It is also a known fact that anything that can go wrong, will go wrong... at some point. Therefore, we need a way to handle the errors that may come as we are trying render our chunks. We also want the error handling in the same declarative way, we are used to in React. In comes error boundaries.
Error boundaries are components with special lifecycle functions like getDerivedStateFromError()
or componentDidCatch()
. These functions are there to catch errors in their child component tree, so you can render an error UI and log the specific error. In our example it will look like this:
const WishList = React.lazy(() => import('./WishList')) const GiftTable = React.lazy(() => import('./GiftTable')) <ErrorBoundary> <Suspense fallback={<h1>We are loading...🎅</h1>}> <WishList /> <GiftTable /> </Suspense> </ErrorBoundary>
Note that as suspense, the ErrorBoundary component does not have to be a direct parent. You can place it higher up in the tree if, for instance, the error UI should hide more than the actual component that crashed.
If you want to learn more about error bounderies and how to write them, Kristofer Selbekk made a smashing article about it in last years calendar. Take a look!
Suspense in the future
This was a "quick" intro to React.Suspense and how it is used with code splitting. However, tomorrow we will see what Suspense will become in the future. A hint – it will not only be used for code splitting, but so much more! We will also take a deep dive into what problems suspense tries to solve and how we structure our code to make a great user experience – so stay tuned!