Suspense helps manage loading states for components that rely on asynchronous operations, such as fetching data or dynamically loading components (code splitting).
How does Suspense work?
React Suspense works by wrapping a component that has asynchronous behavior, and while that component is waiting to load, React will “suspend” rendering and show a fallback UI until the operation is complete.
1. Basic Example of Suspense
Here’s a basic example of how Suspense can be used with a lazy-loaded component.
Step 1: Import React and Suspense
import React, { Suspense, lazy } from "react";
Step 2: Use React.lazy
for Lazy Loading
React provides the React.lazy()
function to dynamically import a component when it’s needed.
const LazyComponent = lazy(() => import("./LazyComponent"));
Step 3: Wrap with Suspense
Now, wrap the lazy-loaded component inside a <Suspense>
component and provide a fallback
UI (such as a loading spinner) until the component finishes loading.
const App = () => { return ( <div> <h1>React Suspense Example</h1> <Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </Suspense> </div> ); }; export default App;
LazyComponent
will be loaded only when needed.- If it takes time to load, Suspense will display the “Loading…” message until the component is ready.
2. Suspense with Data Fetching (React 18+)
In React 18 and beyond, Suspense can also be used with data fetching in conjunction with React’s new concurrent rendering. Suspense helps in delaying rendering until data is ready, providing a smoother user experience.
Using Suspense with Data Fetching
React doesn’t have built-in support for data fetching out of the box with Suspense, but you can integrate it with libraries like Relay or React Query.
Step 1: Create a “Suspense-Friendly” Fetcher
You need to create a function that “throws” a promise when data is not yet available. This is how Suspense knows to suspend rendering.
// Simulated fetch function for data const fetchData = () => { let data = null; const promise = new Promise((resolve) => { setTimeout(() => { data = { message: "Data fetched successfully!" }; resolve(data); }, 2000); // Simulating a delay of 2 seconds }); // Throw the promise while it's loading if (!data) { throw promise; } return data; };
Step 2: Create the Component Using Suspense
Now, use the fetcher inside a component.
const DataComponent = () => { const data = fetchData(); // Data fetching simulated return <h1>{data.message}</h1>; }; const App = () => { return ( <div> <h1>Data Fetching with Suspense</h1> <Suspense fallback={<div>Loading data...</div>}> <DataComponent /> </Suspense> </div> ); }; export default App;
In this example:
fetchData
simulates a delayed fetch and throws a promise.- Suspense will wait for the data to be fetched, and while waiting, it will show the
fallback
UI (e.g., “Loading data…”).
3. Suspense with Multiple Components
You can wrap multiple components with a single <Suspense>
component to handle the loading state for all of them.
const Component1 = lazy(() => import("./Component1")); const Component2 = lazy(() => import("./Component2")); const App = () => { return ( <div> <h1>Suspense with Multiple Components</h1> <Suspense fallback={<div>Loading components...</div>}> <Component1 /> <Component2 /> </Suspense> </div> ); };
React will handle the loading for all components wrapped within the <Suspense>
component.
4. Suspense in Concurrent Mode (React 18)
React 18 introduces Concurrent Mode, which works alongside Suspense to make rendering more efficient and seamless. With Concurrent Mode, Suspense can give React more control over when and how to update the UI, improving performance in large applications.
How Concurrent Mode Changes Suspense:
- Suspense enables interruptible rendering: React can pause rendering, load the data, and continue where it left off without blocking the UI.
- The fallback UI can be shown as React is fetching or processing data, allowing the UI to remain responsive.
5. Performance Optimization with Suspense
Suspense improves performance by allowing code splitting and lazy loading of components, which results in smaller initial JavaScript bundles. Only the necessary components and data are loaded when needed.
Best Practices:
- Lazy Load Routes: Load different pages or parts of the app on demand, rather than loading everything at once.
- Defer Non-Essential Data: Only load data when it’s required to render a component, and display a loading state in the meantime.
- Use Suspense for Libraries: Use Suspense to lazily load heavy libraries or third-party components.