React: From Components to Production · Fetching and Caching Server Data
Suspense and Error Boundaries
Declare loading and error UI around parts of the tree.
Suspense shows a fallback while children are waiting for code (lazy) or data from Suspense-enabled sources (useSuspenseQuery, framework loaders, React 19's use(promise)). Error boundaries catch rendering errors in their subtree and show fallback UI; use react-error-boundary for a function-component API. Place boundaries to keep failures and loading states local, not app-wide.
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { useSuspenseQuery, QueryErrorResetBoundary } from "@tanstack/react-query";
function Profile({ id }: { id: string }) {
const { data } = useSuspenseQuery({ queryKey: ["user", id], queryFn: () => fetchUser(id) });
return <h2>{data.name}</h2>; // data is always defined here
}
export function ProfileSection({ id }: { id: string }) {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary onReset={reset} fallbackRender={({ resetErrorBoundary }) => (
<div role="alert">Couldn't load profile <button onClick={resetErrorBoundary}>Retry</button></div>
)}>
<Suspense fallback={<ProfileSkeleton />}>
<Profile id={id} />
</Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}
Common pitfalls
- One Suspense boundary at the root, so any loading hides the entire page.
- Expecting error boundaries to catch errors in event handlers or async code (they don't).
- Waterfalls: sequential suspending children that could load in parallel.
Key points
- Suspense handles loading declaratively
- Error boundaries catch render errors
- Place boundaries close to the data
Further reading: react.dev: Suspense
Practice questions
Which errors do error boundaries NOT catch?
Answer
Errors in event handlers, asynchronous code (setTimeout, promises outside rendering), server-side rendering, and errors thrown in the boundary itself.
What is a request waterfall with Suspense?
Answer
When a component suspends, its children don't render until it resolves, so their data requests start only afterwards. Start fetches in parallel (prefetch in loaders or a parent) to avoid it.
Open in the app to mark lessons complete, take notes and track your progress.