React Suspense: The Best Way to Handle Loading States


📅 Published at: March 5, 2022

React Suspense has evolved significantly in React 18, making it easier than ever to handle loading states while keeping apps responsive and performant.

In this guide, we’ll cover:
What’s new in Suspense with React 18
How to use Suspense for data fetching
Integrating Suspense with React Query for seamless loading states
Real-world examples & best practices


What Is Suspense in React?

Suspense is a built-in React component that helps manage loading states by pausing rendering until the data is ready.

Instead of using useEffect + useState to show loading indicators, you can wrap components in , and React will automatically handle when they appear.

🔹 React 18 improved Suspense by making it work with data fetching, server components, and React Query.


1️⃣ Basic Example: Suspense with React.lazy() for Code Splitting

The simplest use of Suspense is lazy-loading components with React.lazy():

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

const LazyComponent = lazy(() => import("./MyComponent"));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}

export default App;

How it works:

  • React.lazy() dynamically loads the component when needed.
  • <Suspense fallback={...}> shows a loading state while fetching.
  • Once loading is complete, React automatically replaces the fallback.

2️⃣ Suspense for Data Fetching in React 18

With React 18, Suspense can now handle async data fetching directly—without needing useEffect.

📌 Example: Fetching Data Without Suspense (Traditional Way)

import { useEffect, useState } from "react";

function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("/api/user")
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <div>Loading user...</div>;

  return <h1>Welcome, {user.name}!</h1>;
}

🔴 Problems:

  • Imperative loading logic (manual loading state).
  • Extra renders before showing data.
  • Difficult to scale when multiple components fetch data.

✅ Optimized with Suspense (React 18 Approach)

import { Suspense } from "react";
import { fetchUserData } from "./userAPI";

const userResource = fetchUserData();

function UserProfile() {
  const user = userResource.read();
  return <h1>Welcome, {user.name}!</h1>;
}

export default function App() {
  return (
    <Suspense fallback={<div>Loading user...</div>}>
      <UserProfile />
    </Suspense>
  );
}

How it works:

  • fetchUserData() creates a resource that Suspense can handle.
  • No manual loading states—Suspense automatically handles the delay.
  • Cleaner, more declarative code with fewer side effects.

3️⃣ Using Suspense with React Query

React Query doesn’t use Suspense by default, but enabling it is easy.

📌 Example: Fetching Data with React Query (Without Suspense)

import { useQuery } from "@tanstack/react-query";

function UserProfile() {
  const { data: user, isLoading } = useQuery(["user"], fetchUserData);

  if (isLoading) return <div>Loading user...</div>;

  return <h1>Welcome, {user.name}!</h1>;
}

🔴 Downsides:

  • Still requires manual isLoading checks.
  • Extra conditional logic for handling UI states.

✅ Optimized with Suspense in React Query

Enable Suspense in React Query by passing { suspense: true }:

import { Suspense } from "react";
import { useQuery } from "@tanstack/react-query";

function UserProfile() {
  const { data: user } = useQuery(["user"], fetchUserData, { suspense: true });
  return <h1>Welcome, {user.name}!</h1>;
}

export default function App() {
  return (
    <Suspense fallback={<div>Loading user...</div>}>
      <UserProfile />
    </Suspense>
  );
}

How it works:

  • React Query automatically suspends rendering until data is ready.
  • No need for isLoading checks—Suspense manages it.
  • Loading state is controlled at a higher level instead of inside components.

🛠 When to Use Suspense for Data Fetching?

Good for:
✔️ Fetching critical data before rendering UI
✔️ Avoiding excessive re-renders due to loading states
✔️ Keeping UI clean & declarative

Avoid if:

  • You need more control over loading behavior.
  • The API returns errors often (Suspense doesn’t handle errors natively).

Real-World Use Cases for Suspense

Lazy-loading UI components dynamically
Fetching user data before showing a dashboard
Rendering real-time data updates with React Query
Optimizing large forms or modals that fetch data


Final Thoughts

🔹 React 18 improved Suspense by integrating it with data fetching.
🔹 Suspense + React Query creates a cleaner, more efficient way to handle async data.
🔹 No more loading spinners everywhere—just declarative, seamless UI transitions.

Start using Suspense in your React projects today!

💬 Have you tried Suspense for data fetching yet? Let’s discuss in the comments!


Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *