React Suspense is a powerful feature in React that allows developers to manage asynchronous operations like data fetching, code splitting, and more, in a way that keeps the UI responsive while data is loading. It provides a simple API for handling loading states, and errors while waiting for asynchronous resources, making it easier to build applications with smoother user experiences.
In this guide, we’ll explore React Suspense for data fetching, how it works, and best practices for integrating it into your React applications.
1. What is React Suspense?
React Suspense is a feature in React that lets you suspend rendering until some asynchronous operation (such as data fetching or code splitting) is complete. During this suspension, React can show a fallback UI (like a loading spinner or skeleton screen) while the content is being fetched or loaded.
Suspense helps you avoid complex state management for loading, error, and success states. Instead, you can use it as a declarative way to specify what should happen while waiting for asynchronous data.
2. How Does React Suspense Work?
React Suspense works by wrapping components that rely on asynchronous operations (like data fetching) in a <Suspense>
component. When React encounters a Suspended component, it pauses rendering until the data is ready. Meanwhile, a fallback (such as a loading indicator) is shown.
Basic Structure:
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
<Suspense>
: This is the wrapper that tells React to suspend rendering of its children until certain conditions are met (e.g., data fetching).fallback
: A fallback UI that is displayed while the data or component is loading. This can be anything you like, such as a loading spinner, skeleton, or placeholder.
3. Using Suspense with Data Fetching
The core feature of React Suspense is its integration with asynchronous data fetching. In the past, React wasn’t designed with asynchronous data fetching in mind, but with Suspense, this becomes a much simpler and cleaner process.
To enable Suspense with data fetching, you need to use a Suspense-compatible data fetching library. One of the most common approaches is using React’s Concurrent Mode with a library like React Query, Relay, or Apollo Client (for GraphQL).
However, you can also create a custom data-fetching resource that integrates with Suspense.
Example: Custom Data Fetching with Suspense
import React, { Suspense } from 'react';
// Simulating a data-fetching function
function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve('Hello, world!');
}, 2000);
});
}
// Resource that Suspense will work with
function createResource(promise) {
let status = 'pending';
let result;
let suspender = promise
.then(r => {
status = 'success';
result = r;
})
.catch(e => {
status = 'error';
result = e;
});
return {
read() {
if (status === 'pending') {
throw suspender; // Suspends the component
} else if (status === 'error') {
throw result; // Rethrows the error
} else {
return result; // Returns data when ready
}
}
};
}
// Wrap data-fetching in a Suspense-compatible resource
const resource = createResource(fetchData());
// Component that uses the data
function MyComponent() {
const data = resource.read(); // Will suspend if data is not ready
return <div>{data}</div>;
}
// Using Suspense in the component tree
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
);
}
export default App;
4. Key Concepts for Data Fetching with Suspense
throw suspender
: When React encounters an async operation that hasn’t completed yet, it throws the promise (called the suspender). This tells React to suspend rendering.resource.read()
: Theread()
method checks if the data is ready. If not, it will throw the promise and cause the Suspense fallback UI to show.Suspense
: React will wait for the data to resolve, and once it’s ready, it will render the component with the fetched data.
5. Suspense for Concurrent Data Fetching
Concurrent Mode (still experimental as of now) enhances the React rendering process to be more responsive. By integrating Suspense with Concurrent Mode, React can manage multiple asynchronous data fetches simultaneously and prioritize the rendering of the most important content.
With Concurrent Mode, React can:
- Pause and resume rendering based on the priority of the content.
- Batch multiple async operations together, making the experience smoother for the user.
Example of Concurrent Mode with Suspense:
// React Concurrent Mode is experimental but can be enabled by wrapping the app with <Suspense>
import React, { Suspense } from 'react';
// Simulate two data-fetching operations
const resourceA = createResource(fetchData());
const resourceB = createResource(fetchData());
// Two components that fetch data concurrently
function ComponentA() {
const dataA = resourceA.read();
return <div>{dataA}</div>;
}
function ComponentB() {
const dataB = resourceB.read();
return <div>{dataB}</div>;
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<ComponentA />
<ComponentB />
</Suspense>
);
}
export default App;
In this example, React can fetch data for both components concurrently, making the overall app feel faster.
6. Best Practices for Using Suspense with Data Fetching
a) Handle Errors Gracefully
When an error occurs in a Suspended component, React will throw the error. To handle this, you can use an Error Boundary to catch and display errors in a fallback UI.
import React from 'react';
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
console.log(error, info);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
</ErrorBoundary>
);
}
export default App;
b) Avoid Overloading Suspense with Too Many Components
While Suspense is useful, it is best used sparingly to avoid over-complicating the UI rendering process. Suspense works best when handling a few async resources at a time, rather than wrapping too many components in a single Suspense block.
c) Custom Data Fetching Hooks
Consider creating custom hooks for fetching data that integrates seamlessly with Suspense. For example, using React Query or Apollo Client already offers built-in support for Suspense-like patterns.