ErrorBoundary and Suspense
Difference between Suspense and Error Boundary
| Component | What it does | When it triggers |
|---|---|---|
<Suspense> | Shows a fallback while the component isn't ready yet (for example, loading data or code). | When the component "suspends" (throws a Promise). |
<ErrorBoundary> | Shows a fallback on error, when the component has crashed. | When the component "crashes" (throws an Error). |
React handles both a
Promiseand anErrorthe same way, through a "throw". The difference is what exactly was thrown:
- a
Promisemeans waiting (Suspense)- an
Errormeans showing an error (ErrorBoundary)
How they work together
React goes top to bottom:
- A component can suspend the render (throw a
Promise) -> React looks for the nearest<Suspense>. - If a component crashes (throws an
Error) -> React looks for the nearest<ErrorBoundary>.
So they can, and should, be nested inside each other so that different situations (loading and errors) are handled correctly.
Example of a correct combination
import { Suspense } from "react";
import ErrorBoundary from "./ErrorBoundary";
import { UserProfile } from "./UserProfile";
function App() {
return (
<ErrorBoundary fallback={<h2>Error loading data</h2>}>
<Suspense fallback={<h2>Loading profile...</h2>}>
<UserProfile />
</Suspense>
</ErrorBoundary>
);
}What happens:
- if
UserProfileisn't loaded yet (for example,React.lazy()or a Suspense-enabled request), the fallback fromSuspenseshows ("Loading..."); - if
UserProfilethrows an error (for example,throw new Error()), the fallback fromErrorBoundaryshows ("Error loading data").
An example with React.lazy()
const UserProfile = React.lazy(() => import("./UserProfile"));Here React.lazy() itself throws a Promise while loading,
so Suspense "catches" that promise and shows its fallback.
An example of nested Suspense + ErrorBoundary
You can wrap individual UI areas:
function Dashboard() {
return (
<div>
<ErrorBoundary fallback={<h3>Error in stats</h3>}>
<Suspense fallback={<p>Loading stats...</p>}>
<StatsWidget />
</Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={<h3>Error in profile</h3>}>
<Suspense fallback={<p>Loading profile...</p>}>
<ProfileWidget />
</Suspense>
</ErrorBoundary>
</div>
);
}If StatsWidget breaks, "Error in stats" shows,
while ProfileWidget stays untouched.
If StatsWidget is simply loading, "Loading stats..." shows.
Everything is independent.
A beginner mistake: nesting it the other way
<Suspense fallback={<p>Loading...</p>}>
<ErrorBoundary fallback={<p>Error!</p>}>
<UserProfile />
</ErrorBoundary>
</Suspense>This also works, but there's a nuance: if an error happens before Suspense resolves the Promise, React might not have time to show "loading" and shows "error" right away instead.
So ErrorBoundary usually has to be on the outside, to catch any errors inside the Suspense zone. Though sometimes (for example, with SSR or split loading) it's the other way around.
Summary: who is responsible for what
| Situation | Who handles it | What it shows |
|---|---|---|
| The component is loading (lazy, data) | Suspense | a loading fallback |
The component threw an error (throw new Error()) | ErrorBoundary | an error fallback |
| An error outside React (an event, async code) | - | needs a manual try/catch |
| Both situations are possible | Combined | Suspense while loading -> ErrorBoundary on error |
Recommended pattern (React 18+)
<ErrorBoundary fallback={<ErrorUI />}>
<Suspense fallback={<LoadingUI />}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>This way:
- Loading ->
LoadingUI - Error ->
ErrorUI - Successful result ->
AsyncComponent
A pattern for UI zones (best approach for large applications)
| UI zone | ErrorBoundary | Suspense |
|---|---|---|
| The main page | a shared ErrorBoundary | a shared Suspense |
| Individual widgets | local ErrorBoundaries | local Suspense |
| Critical zones (checkout, auth) | separate ErrorBoundaries | Suspense for data |
Summary
Yes, ErrorBoundary and Suspense work great together. Use:
<Suspense>for loading (when the component isn't ready yet),<ErrorBoundary>for errors (when the component has crashed).
Recommended nesting order:
<ErrorBoundary>
<Suspense>
<Component />
</Suspense>
</ErrorBoundary>This pattern:
- isolates errors;
- shows a fallback UI during loading and failures;
- makes the interface resilient and smooth.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.