Nishil Patel
Feb 7, 2024
5 min read
ReactJS Suspense lets you control how your components render while waiting for data or code to load. You can show placeholders, unveil components progressively, display cached content, signal transitions, handle errors, and whatnot, to make your React app even more performant. In this article, you are gonna learn how Suspense can help you work better with async data.
1.
What is React Suspense?
2.
What problem does React Suspense solve?
3.
What are the common scenarios for using Suspense?
4.
How to use React Suspense for lazy loading?
5.
How to use React Suspense for data fetching?
6.
What are the top benefits of using React Suspense?
7.
What are the updates added for React Suspense in React 18?
8.
FAQs
Suspense is a built-in React component that acts as a wrapper for the child components to “hold off” or “suspend” their rendering with a fallback UI until an action has finished. These actions could be data getting fetched, code being loaded (like lazy-loaded components), or any other async operations. While the child components are waiting to show, you can display a fallback UI, such as a loading spinner, a skeleton UI, or any other placeholder you define.
<Suspense fallback={<Spinner />}>
<MyComponent />
</Suspense>
React Suspense was initially introduced with React version 16.6, primarily for code-splitting with React.lazy. It can work with any data fetching method, as long as it can signal its loading state by throwing a promise (though in modern React 18+, the use Hook simplifies this greatly).
React Suspense isn’t a data-fetching library like Axios, or a state management library like Redux or Redux Toolkit. It’s a method that lets you express how your UI components depend on async data or code, and how to handle the loading state in a clear, declarative way.
One of the trickier parts of building apps is handling async network requests and large JavaScript bundles. It can be frustrating when there are delays, errors, and inconsistencies in the UI while data is being fetched from an API, a database, or a file, or when the entire app code needs to be downloaded before anything is displayed. This can negatively affect the user experience and the performance of the app.
Before React Suspense, the data fetching behavior was usually managed by third-party state management libraries or by manually handling loading states with isLoading booleans and conditional rendering. Similarly, code splitting often required a more complex setup. While these methods have their benefits, the boilerplate and the data/code handling workflows across multiple modules or files can be long, complicated, and prone to errors.
React introduced Suspense to beautifully tackle this problem and offer a more streamlined, declarative way to handle async data and code loading states, deeply integrated into React's rendering model.
You can use React Suspense to:
Showing a standby UI while data or code is loading up in the background makes things smoother for the user. It gives them a heads-up that something’s happening, keeps the layout from jumping around, and chills out any worries they might have.
The main part of the app shows up first, and the smaller parts come in as they get loaded up. This speeds up your app and keeps the screen from getting stuck.
The cached data sticks around until the new data comes in. This way, you avoid any loading screens and the screen doesn’t flicker.
Also Read: How to clear DNS Cache on Chrome using chrome://net-internals/#dns
You can pop up a special transition screen when the user moves around or does something that needs fresh data or code. This makes it clear what’s going on and cuts down on any mix-ups.
You can also handle situations where pulling in the data doesn’t work out or the data’s gone missing on the server. This keeps your app from crashing and shows a fitting error message or a standby screen (often in conjunction with an Error Boundary).
Lazy loading, also known as code splitting, allows you to defer the loading of certain parts of your app’s code until they are actually needed. This significantly reduces the initial bundle size and improves the app’s startup performance. React Suspense, in conjunction with React.lazy, provides a built-in way to do this.
Here’s how you can use React Suspense for lazy loading:
Instead of a regular import statement, you'd use React.lazy() with a dynamic import() call. This tells React that this component's code should be loaded only when it's rendered.
// MyComponent.js file
function MyComponent() {
return <div>This component was lazy loaded!</div>;
}
export default MyComponent; // Must be a default export
// App.js file
import React, { Suspense, lazy } from "react";
// Lazy load MyComponent
const LazyLoadedComponent = lazy(() => import("./MyComponent"));
function App() {
const [showComponent, setShowComponent] = React.useState(false);
return (
<div>
<h1>App</h1>
<button onClick={() => setShowComponent(!showComponent)}>
{showComponent ? "Hide Component" : "Show Component"}
</button>
{showComponent && (
// Wrap the lazy-loaded component with Suspense
<Suspense fallback={<div>Loading component...</div>}>
<LazyLoadedComponent />
</Suspense>
)}
</div>
);
}
export default App;
In this example, the code for MyComponent.js will only be fetched from the network when showComponent becomes true and LazyLoadedComponent attempts to be rendered. While it's loading, "Loading component..." will be displayed. Once loaded, LazyLoadedComponent will appear.
This pattern is perfect for:
You can use the <Suspense> component to wrap around components that rely on async data or code. You’ll need to provide a fallback prop that specifies what should be rendered while the data or code is loading. Your data fetching logic (or a library supporting Suspense) should signal to React when data isn't ready.
Before React 18, you’d write code that threw a promise when the data wasn’t ready yet, and resolved it when the data was available. However, with React 18+, the use Hook greatly simplifies this and is the recommended way to include Suspense with data fetching libraries. Plus, in a real app, you'd typically use a Suspense-enabled data fetching library or the use Hook (covered in the React 18 updates in the later section) which handles this complexity for you.
Let's look at a basic example of how a component could signal to Suspense that it's waiting for data.
This example shows the basics of how Suspense works by "throwing a promise".
import React, { Suspense } from "react";
// For instance, consider the following as a helper
// from a data-fetching library.
// It tries to read data from a cache. If not found, it "suspends"
// by throwing a promise that resolves when data is fetched.
const fetchDataFromApi = () => {
// This is a highly simplified example!
// In a real app, this would interact with a cache or fetcher.
let data = null;
let promise = null;
// Simulate data not being ready initially
if (!data) {
promise = new Promise((resolve) => {
setTimeout(() => {
data = "Hello from fetched data!"; // Data is now ready
resolve();
}, 3000); // Simulate 3-second fetch time
});
throw promise; // Suspend the component
}
return data;
};
// A component that needs fetched data
function DataDisplayComponent() {
const data = fetchDataFromApi(); // This call will "suspend"
return <h2>{data}</h2>;
}
// The App component wraps DataDisplayComponent with Suspense
function App() {
return (
<div className="App">
<h1>Data App</h1>
<Suspense fallback={<div>Loading data...</div>}>
<DataDisplayComponent />
</Suspense>
</div>
);
}
export default App;
When the data is ready, the DataDisplayComponent resumes rendering and Voila! It displays the fetched content.
Using Suspense in React packs several benefits for you to improve the user experience and performance of your React app. Here are some of them:
React Suspense lets you specify the loading state of your components in a declarative way, without having to use conditional rendering or manual state variables for loading flags. It also ensures that the loading state is consistent across the component tree, by rendering the fallback UI only when the data is not available for all the components in the Suspense boundary.
React Suspense, especially when combined with useTransition, allows you to control the timing and sequencing of your component transitions. You can specify how long to wait before showing the fallback UI, and how long to keep showing the fallback UI after the data is ready. This way, you can avoid showing unnecessary spinners or flashes of content and create smooth and seamless transitions and animations for your components.
React Suspense itself doesn't directly handle errors, but it's designed to work hand-in-hand with Error Boundaries. The <ErrorBoundary> component is similar to the <Suspense> component, but it catches errors (including those thrown by promises that reject) and renders a fallback UI that can display an error message or a retry button. You typically wrap your <Suspense> component with an <ErrorBoundary> to catch any data fetching or rendering errors.
React Suspense is developed to work with the concurrent features of React (formerly "Concurrent Mode"), which enable React to render your components in a non-blocking way and prioritize the updates that are most important for the user experience. Suspense integrates with these concurrent features and allows you to take advantage of them.
Time slicing, automatic batching, and selective hydration are some of the features that React offers to improve the performance and user experience of web apps.
Here’s a brief explanation for each:
The core concepts of Suspense remain true for all supported versions (starting from version 16.6), but with the stable release of React 18, its integration into the ecosystem has matured significantly, especially for data fetching.
Here are the key updates:
In React 18 and later, "Concurrent Mode" is no longer an experimental feature. Instead, Concurrent Features are now built-in and enabled by default when you use createRoot. You no longer opt into a special "mode".
Features like time slicing, automatic batching, and transitions are now integral to how React works. This means Suspense is not just an add-on but a core piece of the React rendering model, working seamlessly to enable non-blocking rendering and smoother user interfaces.
While throwing a promise from a component is the underlying mechanism that makes Suspense work, React now provides a much simpler and cleaner API for this: the use Hook.
The use Hook can be called inside Render Components and Hooks to read the value of a resource, such as a Promise or a context. When you pass a promise to use, it handles the "suspending" for you:
Here is how a component using data can be simplified with the use Hook:
import { Suspense, use } from "react";
// Imagine this promise comes from a data fetching mechanism.
// It will resolve with some data after a delay.
const myDataPromise = new Promise((resolve) => {
setTimeout(() => {
resolve("Data is here!");
}, 3000); // Simulate 3-second fetch
});
function MyComponentWithData() {
// The 'use' hook unwraps the promise.
// If the promise is pending, this will "suspend" the component.
const data = use(myDataPromise);
return <h2>{data}</h2>;
}
function App() {
return (
<div>
<h1>App with Data</h1>
<Suspense fallback={<div>Loading data with use hook...</div>}>
<MyComponentWithData />
</Suspense>
</div>
);
}
export default App;
One of the most significant advancements related to Suspense is the introduction of React Server Components (RSC). With Server Components, data fetching can happen on the server before the component even renders on the client.
Suspense plays a critical role here by allowing you to stream HTML from the server to the client.
The useTransition Hook has become a key part of the modern Suspense workflow, especially for handling UI changes during data mutations (like form submissions) or navigation. useTransition lets you mark a state update as a "transition," which tells React that it's not urgent and can be interrupted.
When used with Suspense, it allows you to show pending feedback without replacing the existing UI. For example, when a user clicks a button to refresh data:
Nishil is a successful serial entrepreneur. He has more than a decade of experience in the software industry. He advocates for a culture of excellence in every software product.
Meet the Author: Nishil Patel, CEO, and Co-founder of BetterBugs. With a passion for innovation and a mission to improve software quality.
We never spam.
Share your experience with the founderhere!