Concurrent React: Suspense and Lazy Loading 🚀
The Restaurant Analogy 🍽️
Imagine you’re at a busy restaurant. When you order food, you don’t have to wait at the kitchen door. Instead, you sit at your table, maybe sip some water, and the waiter brings your food when it’s ready.
React Suspense works exactly like this restaurant!
- Your app is the restaurant
- Components are the dishes you order
- Suspense is the waiter who handles the waiting
- Fallbacks are the bread basket while you wait
1. Suspense Fundamentals
What is Suspense?
Suspense is React’s way of saying: “Hey, some stuff isn’t ready yet. Let me show something else while we wait!”
Think of it like a magic placeholder. When your app needs to load something (like data or a component), Suspense shows a temporary thing until the real thing arrives.
Simple Example
import { Suspense } from 'react';
function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<SlowComponent />
</Suspense>
);
}
What happens here?
- React tries to show
SlowComponent - If it’s not ready, React shows “Loading…”
- When ready, the real component appears!
2. Suspense Boundaries
What is a Suspense Boundary?
A boundary is like a fence around your garden. Everything inside the fence follows the same rules.
A Suspense boundary catches any “loading” state from components inside it.
graph TD A["App"] --> B["Suspense Boundary"] B --> C["Loading Component A"] B --> D["Loading Component B"] B --> E["Loading Component C"] style B fill:#f9f,stroke:#333,stroke-width:2px
Why Use Boundaries?
Without boundaries, your whole app might show a spinner. With boundaries, only the loading part shows a spinner.
Multiple Boundaries Example
function App() {
return (
<div>
<Header /> {/* Shows immediately */}
<Suspense fallback={<p>Loading posts...</p>}>
<Posts /> {/* Has its own loading */}
</Suspense>
<Suspense fallback={<p>Loading comments...</p>}>
<Comments /> {/* Separate loading */}
</Suspense>
</div>
);
}
Result: Header shows instantly. Posts and Comments load separately!
3. Suspense Fallbacks
What is a Fallback?
A fallback is what your users see while waiting. It’s like the coming soon sign at a movie theater.
Good Fallback Examples
// Simple text
<Suspense fallback={<p>Loading...</p>}>
// Spinner component
<Suspense fallback={<Spinner />}>
// Skeleton screen (looks like content)
<Suspense fallback={<ContentSkeleton />}>
The Best Fallbacks
Skeleton screens are the best! They show the shape of content before it loads.
function PostSkeleton() {
return (
<div className="skeleton">
<div className="skeleton-title" />
<div className="skeleton-text" />
<div className="skeleton-text" />
</div>
);
}
// Usage
<Suspense fallback={<PostSkeleton />}>
<Post />
</Suspense>
4. The use Hook
What is use?
The use hook is brand new in React 19. It lets you read promises and context directly inside components.
Think of it as a magic reader that:
- Waits for promises automatically
- Works with Suspense out of the box
- Makes async code look like normal code
Example with use
import { use, Suspense } from 'react';
function Posts({ postsPromise }) {
// use() waits for the promise!
const posts = use(postsPromise);
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
function App() {
const postsPromise = fetchPosts();
return (
<Suspense fallback={<p>Loading...</p>}>
<Posts postsPromise={postsPromise} />
</Suspense>
);
}
Key Rules for use
- âś… Can be called inside
ifstatements - âś… Can be called inside loops
- ❌ Cannot be called outside components
- ⚠️ Only works with Suspense
5. React.lazy
What is React.lazy?
React.lazy is like ordering a pizza delivery. You don’t go to the pizza shop yourself. Instead, you call them and they bring it to you when it’s ready.
With React.lazy, components load only when needed.
Basic Example
import { lazy, Suspense } from 'react';
// This doesn't load immediately!
const HeavyChart = lazy(() =>
import('./HeavyChart')
);
function Dashboard() {
return (
<Suspense fallback={<p>Loading chart...</p>}>
<HeavyChart />
</Suspense>
);
}
Why Use React.lazy?
- 📦 Smaller initial download - Users download less at start
- ⚡ Faster loading - App appears quicker
- 💾 Saves bandwidth - Only load what’s needed
6. Dynamic Imports
What are Dynamic Imports?
Normal imports load everything at once:
// Loads immediately when app starts
import HeavyLibrary from 'heavy-library';
Dynamic imports load on demand:
// Loads only when this code runs
const HeavyLibrary = await import('heavy-library');
The Magic of import()
import() returns a promise. It’s like saying “go fetch this, and tell me when it’s here.”
async function loadEditor() {
// Only loads when function is called
const module = await import('./RichTextEditor');
return module.default;
}
Common Pattern
const LazyComponent = lazy(() => import('./MyComponent'));
This combines:
import()- Dynamic loadinglazy()- React integrationSuspense- Loading state handling
7. Code Splitting Basics
What is Code Splitting?
Imagine a huge book. Instead of carrying the whole book everywhere, you only bring the chapter you’re reading.
Code splitting breaks your app into smaller pieces (called chunks).
graph TD A["Your Big App"] --> B["Main Bundle"] A --> C["Admin Bundle"] A --> D["Chart Bundle"] A --> E["Editor Bundle"] style A fill:#ffcc00,stroke:#333
Before Code Splitting
📦 bundle.js (2.5 MB) - Everything in one file
├── React
├── Your components
├── Chart library
├── Editor library
└── Admin pages
After Code Splitting
📦 main.js (200 KB) - Only essentials
📦 charts.js (500 KB) - Loads when needed
📦 editor.js (800 KB) - Loads when needed
📦 admin.js (300 KB) - Loads when needed
How to Split Code
// Create separate chunks automatically
const Charts = lazy(() => import('./Charts'));
const Editor = lazy(() => import('./Editor'));
const Admin = lazy(() => import('./Admin'));
Each import() creates a new chunk (separate file).
8. Route-Based Splitting
The Best Place to Split
Routes are perfect for code splitting! Why?
- Users only visit one page at a time
- Pages are often independent
- Natural loading points (page navigation)
Example with React Router
import { lazy, Suspense } from 'react';
import {
BrowserRouter,
Routes,
Route
} from 'react-router-dom';
// Lazy load each page
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() =>
import('./pages/Dashboard')
);
function App() {
return (
<BrowserRouter>
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
The Result
graph TD A["User visits /"] --> B["Loads Home chunk only"] A --> |Later clicks About| C["Loads About chunk"] A --> |Later clicks Dashboard| D["Loads Dashboard chunk"] style B fill:#90EE90 style C fill:#ADD8E6 style D fill:#FFB6C1
Quick Summary 🎯
| Concept | What It Does | Like… |
|---|---|---|
| Suspense | Handles loading states | A waiter at restaurant |
| Boundaries | Isolate loading areas | Fences around gardens |
| Fallbacks | Shows while loading | “Coming soon” sign |
| use hook | Reads promises easily | Magic promise reader |
| React.lazy | Loads components later | Pizza delivery |
| Dynamic import | Loads code on demand | Ordering when hungry |
| Code splitting | Breaks app into chunks | Book chapters |
| Route splitting | Splits by pages | One chapter per trip |
Your React App is Now Faster! 🚀
You learned how to:
- âś… Wrap slow stuff in Suspense
- âś… Create smart boundaries
- âś… Show helpful fallbacks
- âś… Use the new
usehook - âś… Lazy load with React.lazy
- âś… Split code into chunks
- âś… Optimize routes
Remember the restaurant: Good service means customers don’t stare at the kitchen. Good React apps show something useful while loading!
