React 19 use API
Unwrap promises and consume context conditionally in render with the revolutionary React 19 use API.
TL;DR
- Unwrap asynchronous promises directly in render with
use. - Read context conditionally inside
ifstatements and loops. - Coordinate promise resolution seamlessly with parent
Suspenseboundaries.
Unwrapping Promises with use
Direct Promise ReadingRead a resolved promise synchronously.
function Comments({ promise }: Props) {
const list = use(promise);
return <ListView items={list} />;
}Suspense IntegrationSuspends the component until the promise resolves.
<Suspense fallback={<CardSkeleton />}>
<Comments promise={fetchComments()} />
</Suspense>Streaming Server PromisePass a preloaded promise from a Server Component.
// Server: pass promise without await
<ClientFeed stream={getData()} />Conditional Context Reading
Context in if StatementRead context only when a condition is met.
function Header({ themed }: Props) {
if (!themed) return <Text>Plain</Text>;
const t = use(ThemeContext);
return <CardView color={t.bg} />;
}Context in LoopCall use inside array iteration.
const configs = ctxList.map((c) => use(c));Dynamic Context SelectionPick which context to read based on props.
const val = use(isAdmin ? AdminCtx : UserCtx);use API Rules and Gotchas
External Promise CreationInstantiate promise outside render lifecycle.
// GOOD: Promise created in loader or server
const userPromise = fetchUser(userId);Render Loop AntipatternInstantiating promise inside render loops infinitely.
// BAD: Creates new promise every render
const data = use(fetch('/api'));Error Boundary HandlingCatch rejected promises with standard ErrorBoundary.
<ErrorBoundary fallback={<Text>Load failed</Text>}>
<Suspense fallback={<Skeleton />}>
<DataView promise={p} />
</Suspense>
</ErrorBoundary>Streaming Data Architectures
Parallel Promise UnwrappingUnwrap multiple data streams independently.
const user = use(userPromise);
const stats = use(statsPromise);Selective FallbacksIsolate individual promise suspensions cleanly.
<Suspense fallback={<UserSkeleton />}>
<UserCard promise={uPromise} />
</Suspense>Client Action Cache PassingPass refreshed promise from action into use.
const nextPromise = refetch();
setPromise(nextPromise);Tips
- Call
use(promise)to unwrap values directly during component render, letting parent Suspense boundaries handle pending fallback states. - Leverage
use(ThemeContext)inside conditional branches or loops where standard Hook rules previously disallowed useContext calls.
Warnings
- Never create promises inside component render bodies passed to
use; promises must be created outside or in server components. - Always ensure components invoking
use(promise)are wrapped inside a valid<Suspense>boundary to prevent uncaught suspensions.
In Practice
Profile card that reads theme context conditionally and unwraps a user promise.
- Read ThemeContext only when a prop asks for it.
- Unwrap the user promise with use() during render.
const ThemeCtx = createContext({ dark: true });
function Profile({ promise, styled }: ProfileProps) {
const theme = styled ? use(ThemeCtx) : null;
const user = use(promise);
return (
<CardView dark={theme?.dark}>
<CardTitle>{user.name}</CardTitle>
<Text>{user.email}</Text>
</CardView>
);
}FAQ
No. Unlike standard hooks, use() can be called conditionally inside if blocks and loops, although it must still be called during component rendering.
Yes, when paired with Suspense. Rather than fetching in useEffect and setting state, you pass a promise directly into your component and unwrap it with use().
Creating promises inside render generates a brand new promise reference on every pass, causing an infinite suspension loop when unwrapped with use().