The use() Hook & Suspense in Practice
Read promises and context with use(), and let Suspense own your loading UI declaratively.
React 19's use reads a resource during render — a promise or a context. When you hand it a promise, the component suspends until that promise settles, and the nearest <Suspense> boundary shows its fallback in the meantime. The result: no loading boolean, no useEffect fetch dance, no manual state machine for the happy path.
What is different about use()
- It can be called conditionally — inside an
ifor a loop — unlike every other hook. - It participates in Suspense (for promises) and reads context (as a flexible alternative to
useContext).
The one rule that trips everyone up
Do not create the promise inline in the component body:
// WRONG: a brand new promise every render -> infinite suspense loop
function Profile() {
const user = use(fetch('/api/me').then(r => r.json()));
return <p>{user.name}</p>;
}The promise must be stable across renders — created by a parent, a cache, or a framework loader — and passed down as a prop. Otherwise each render suspends on a fresh promise and never resolves.
Suspense + Error Boundary = the full story
Suspense handles pending; it does not handle rejected. Wrap the boundary in an Error Boundary so a failed fetch shows a real error UI instead of an endless spinner. Together they give you three states — loading, error, success — declaratively, with the success path written as if the data were always there.
Example
import { use, Suspense, useState } from 'react';
// Cache promises by key so the SAME promise is reused across renders.
const cache = new Map();
function fetchUser(id) {
if (!cache.has(id)) {
cache.set(
id,
fetch(`/api/users/${id}`).then((r) => {
if (!r.ok) throw new Error('User not found');
return r.json();
})
);
}
return cache.get(id);
}
function UserName({ id }) {
const user = use(fetchUser(id)); // suspends until resolved
return <strong>{user.name}</strong>;
}
function Profile() {
const [id, setId] = useState(1);
return (
<div>
<button onClick={() => setId((n) => n + 1)}>Next user</button>
{/* Fallback shows while the stable promise is pending */}
<Suspense fallback={<em>Loading user...</em>}>
<UserName id={id} />
</Suspense>
</div>
);
}When to use it
- A React 19 server component passes a data promise to a client component that calls use() so the component suspends automatically instead of managing a loading boolean.
- A parallel data-loading pattern creates multiple promises simultaneously and passes each to a separate use() call so all requests fire at once and the page renders as each resolves.
- A feature flag system wraps its config fetch in a promise that is cached at the module level and shared via use() so all components read the same resolved value without multiple fetches.
More examples
use() suspending on a promise
Passes a fetch promise to use() inside a Suspense boundary; the component suspends automatically until the data arrives.
import { use, Suspense } from 'react';
function ArticleContent({ promise }) {
const article = use(promise); // suspends until resolved
return <article><h1>{article.title}</h1><p>{article.body}</p></article>;
}
function ArticlePage({ id }) {
const promise = fetch(`/api/articles/${id}`).then(r => r.json());
return (
<Suspense fallback={<div className="skeleton" />}>
<ArticleContent promise={promise} />
</Suspense>
);
}Parallel data loading
Creates both promises before entering Suspense so they run in parallel, not sequentially.
import { use, Suspense } from 'react';
function Dashboard({ userId }) {
// Both fetches start simultaneously
const userPromise = fetchUser(userId);
const statsPromise = fetchStats(userId);
return (
<Suspense fallback={<p>Loading dashboard...</p>}>
<UserHeader promise={userPromise} />
<StatsPanel promise={statsPromise} />
</Suspense>
);
}
function UserHeader({ promise }) {
const user = use(promise);
return <h1>{user.name}</h1>;
}
function StatsPanel({ promise }) {
const stats = use(promise);
return <p>Posts: {stats.postCount}</p>;
}use() with context conditionally
Calls use() inside a conditional branch — something useContext cannot do — without violating rules of hooks.
import { use, createContext } from 'react';
const FeatureCtx = createContext({ darkMode: false });
function ThemeIcon({ show }) {
// use() CAN be called inside a condition — unlike useContext
if (!show) return null;
const { darkMode } = use(FeatureCtx);
return <span>{darkMode ? '🌙' : '☀️'}</span>;
}
Discussion