How to Implement React Suspense

React Suspense is a powerful built-in React feature that simplifies how you handle loading states for asynchronous operations. This article provides a direct, step-by-step guide on how to implement React Suspense in your applications, focusing on component lazy loading and asynchronous data fetching.

Understanding React Suspense

React Suspense works by wrapping asynchronous components. When a child component is waiting for an asynchronous task to resolve (like loading code or fetching data), Suspense intercepts the rendering process and displays a fallback UI, such as a spinner or a skeleton screen, until the task is complete.

The basic syntax looks like this:

import React, { Suspense } from 'react';

<Suspense fallback={<LoadingSpinner />}>
  <AsyncComponent />
</Suspense>

1. Implementing Suspense for Code Splitting (Lazy Loading)

The most common use case for Suspense is code splitting. This allows you to load specific React components only when they are needed, reducing the initial bundle size of your application.

Step 1: Import lazy and Suspense

First, import lazy and Suspense from the React library.

Step 2: Dynamically Import Your Component

Use React.lazy to import the component dynamically.

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

// Dynamically import the heavy component
const HeavyChartComponent = lazy(() => import('./HeavyChartComponent'));

function Dashboard() {
  return (
    <div>
      <h1>User Dashboard</h1>
      
      {/* Step 3: Wrap the component in Suspense */}
      <Suspense fallback={<div>Loading chart...</div>}>
        <HeavyChartComponent />
      </Suspense>
    </div>
  );
}

export default Dashboard;

2. Implementing Suspense for Data Fetching

To use Suspense for data fetching, you need a data source or library that is compatible with Suspense (such as TanStack Query, SWR, Relay, or Next.js). Suspense works under the hood by catching promises thrown by these data-fetching libraries.

Here is how you implement it using a modern Suspense-compatible query approach.

Step 1: Enable Suspense in Your Data Fetching Library

If you are using a library like TanStack Query (React Query), you can enable Suspense in your queries:

import { useSuspenseQuery } from '@tanstack/react-query';

function UserProfile() {
  // This hook throws a promise that Suspense will catch
  const { data } = useSuspenseQuery({
    queryKey: ['user'],
    queryFn: fetchUserData,
  });

  return (
    <div>
      <h2>{data.name}</h2>
      <p>{data.email}</p>
    </div>
  );
}

Step 2: Wrap the Component in Suspense

In the parent component, render the data-fetching component inside a <Suspense> boundary.

import React, { Suspense } from 'react';
import UserProfile from './UserProfile';

function App() {
  return (
    <div className="app">
      <h1>My Application</h1>
      
      <Suspense fallback={<p>Loading user profile...</p>}>
        <UserProfile />
      </Suspense>
    </div>
  );
}

Handling Errors with Error Boundaries

When using Suspense for data fetching, network requests can fail. Because Suspense only handles the loading state, you must pair it with an Error Boundary to handle the error state.

import React, { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import UserProfile from './UserProfile';

function App() {
  return (
    <div className="app">
      {/* Catch and display errors if the fetch fails */}
      <ErrorBoundary fallback={<div>Failed to load profile. Please try again.</div>}>
        {/* Catch the loading state while fetching */}
        <Suspense fallback={<div>Loading...</div>}>
          <UserProfile />
        </Suspense>
      </ErrorBoundary>
    </div>
  );
}