How to use React Suspense in Next.js

Cover Image for How to use React Suspense in Next.js
Ali Bentaleb

What is React Suspense

In React, Suspense is an API introduced since version 16.6 that wait for some code to load and display a teaser or a spinner while waiting, more technically it specifies fallback in case children have suspended, and can handle errors with declaring an error boundary class that works the same as the componentDidCatch to handle errors in case the rendering has some issues.

React recommends implementing this ErrorBoundary class once and using it in the entire application.

Configure Next.js to use React Suspense

First, we need to enable concurrent features in Next.js to use React.lazy and Suspense, in order to achieve it we need to:

  1. Create a new Next.js project if not already done with:
npx create-next-app
  1. Get the latest version of Next.js, react, and react-dom with the command:
npm i --save react@latest react-dom@latest next@12

  1. Enable concurrent mode by adding concurrentFeatures on the Next.js config file next.config.js like so:
module.exports = {
  reactStrictMode: true,
  concurrentFeatures: true,
};

Second, we need to have lazy components that will be wraped in React Suspense components.

Lazy components have two ways to be declared in Next.js using either:

  • next/dynamic
  • React.lazy

Let’s see how to use both of them and what are their main differences.

With next/dynamic

To declare components with it you need to:

  1. Import your React components with next/dynamic to lazily load them, optionally you can add suspense flag.

Here is what the code will look like.

import dynamic from 'next/dynamic';
const SuspenseComponent = dynamic(
	() => import('../components/SuspenseComponent'),
	{suspense: true}
);
  • This will enable the component to be imported when needed as React.lazy would do.

  • The imported component is compatible inside React Suspense components.

  • The use of props is also possible like any React component.

  1. Create our other components, which are:
  • The page that will be served.
  • The component inside React Suspense.
  • The Spinner.

Now let’s see in details how we can implement each of these elements

Create the served page

Let’s create our served page.

Create pages/home.js and put inside the code below

import Spinner from '../components/spinner';
import {Suspense} from 'react';
import dynamic from 'next/dynamic';

import {useRouter} from 'next/router';

const SuspenseComponent = dynamic(
	() => import('../components/suspenseComponent'),
	{suspense: true}
);

export default function Home() {
	// get URL parameters
	const {query: queryParams} = useRouter();
	//default to 1 if no parameter
	const first = queryParams.first != undefined ? queryParams.first : 1;
	//our suspenseCompnent is rendered with the URL parameter
	return (
		<Suspense fallback={<Spinner />}>
			{/* A component that uses Suspense-based */}
			<SuspenseComponent value={first} />
		</Suspense>
	);
}

We import our SuspenseComponent dynamically using next/dynamic, with next/router we pass parameters directly from the URL and render our component inside the React Suspense, we have also provided a fallback in case the data need time to be fetched and show a Spinner instead.

Create the component inside the React Suspense

Our SuspenseComponent implemented inside the React Suspense look like

File components/SuspenseComponent

// we define two variables, one holding data, and one holding a value if the promise
//was resolved
let myData = {};
let promise = {};
// our React component which will be rendered inside Suspense boundaries
const SuspenseComponent = ({value}) => {
	//our method which will be fired when we call the component
	const waitForData = () => {
		//if data already there, we return it and render is done, if not the Spinner is spinning
		if (myData[value] !== undefined) return myData[value];
		// an array to remember already computed values
		if (!promise[value])
			promise[value] = fetch(
				'https://jsonplaceholder.typicode.com/todos/' + `${value}`
			)
				.then((res) => res.json())
				.then((d) => (myData[value] = d));
		// we can also throw a new Promise with rejection like below
		// throw new Promise((undefined,rej)=>rej())
		throw promise[value];
	};

	const data = waitForData();
	// we fill in what is returned from the typicode API, if no data returned we display
	// a fixed message
	return (
		<div className="w-full flex justify-center items-center h-screen">
			{data.title || 'Hello Ali'}
		</div>
	);
};

The component defines a function which return a Promise rejection in case data is not available yet, The React Suspense in this case render the component provided in the fallback, which is the Spinner, the latter starts to spin and React Suspense tries again to get the data, once OK, the data is stored in myData variable, and then it renders the component with its data.

In the example above, we have rejected the promise in case the data is not available yet, we can also create a new Promise and reject it with throw new Promise((undefined,rej)=>rej()).

Suspense is heavily based on promises, and i recommend to understand promises in this link

Create the fallback Spinner

The Spinner is the component which will be rendered as a fallback when the data fetching is ongoing.

To make it using tailwindcss, check my article in this link for full details.

Touch a file components/spinner.js and copy the below for this custom tailwind animation

export default function Spinner() {
	// animation effect while waiting for rendering
	return (
		<span className="h-screen w-full flex justify-center items-center">
			<span className="animate-ping relative flex h-10 w-10  rounded-full bg-purple-400 opacity-75"></span>
		</span>
	);
}

Test our code

Now you can try to run it in the browser with typing

http://localhost:3000/home?first=5

You should see a little purple circle spinning while React tries to get the data and then the final result once it finally gets the data and render everything.

With React.lazy

React.lazy function should renders a dynamic import as a regular import, and it has the following properties:

  • Its argument is a function that must call a dynamic import.
  • Available only for client side rendering.
  • It must return a promise which resolves to the default export which should be a React component.

Now, we can redo the same code with React.lazy by changing the following in pages/home.js and keep the rest as it is

import {lazy} from 'react';

const SuspenseComponent = lazy(() => import('../components/suspenseComponent'));

Try it again in the browser and see if it gets the same results.

SuspenseList with Next.js

We can do a little bit more by using SuspenseList this time, which is a React API controlling ordering of siblings in React Suspense items, it accepts a parameter of revealOrder to determine the order in which the React Suspense items will be rendered. revealOrder accept as a value

  • together:to render components together.
  • forwards:to render from top to bottom.
  • backwards:to render from bottom to top.

To use it with our previous components, edit pages/home.js or clone as mentioned as above

import {Suspense, SuspenseList, lazy} from 'react';

const SuspenseComponent = lazy(() => import('../components/suspenseComponent'));

const second = queryParams.second != undefined ? queryParams.second : 2;

return (
	<SuspenseList revealOrder="backwards">
		<Suspense fallback={<Spinner />}>
			{/* A component that uses Suspense-based */}
			<SuspenseComponent value={first} />
		</Suspense>

		<Suspense fallback={<Spinner />}>
			{/* A component that uses Suspense-based */}
			<SuspenseComponent value={second} />
		</Suspense>
	</SuspenseList>
);

Differences between next/dynamic and React.lazy

Using either next/dynamic or React.lazy looks equivalent, but there are some differences.

  • Server side rendering or (SSR) is only possible with next/dynamic and not with React.lazy.
  • Each React component has a typeof which is defined by the Symbol function in React, the next/dynamic typeof is a React forward reference while in React.lazy is a react.lazy.
  • next/dynamic can allow named export and default export for a React component while with React.lazy only default export is allowed.

This project can be found in my repository using:

git clone https://github.com/abengit/suspensenextjs.git

Conclusion

We have seen how to use React Suspense in Next.js with both next/dynamic and React.lazy, We also present a use case using SuspenseList and present the differences between dynamic and lazy. The source code is available in GitHub as well.

Check it out and let me know if you love any of the features on it, and i will be glad to share with you the steps to do like it.

How to easily submit a form in...How to build animated spinner ...