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 resolved promise data synchronously in render.
function Comments({ commentsPromise }: Props) {
const list = use(commentsPromise);
return <ListView items={list} />;
}Suspense Boundary IntegrationSuspends component until promise completes.
<Suspense fallback={<CardSkeleton />}>
<Comments commentsPromise={fetchComments()} />
</Suspense>Streaming Server PromiseReceive preloaded promise from Server Component.
// In Server Component: pass promise without await
<ClientFeed stream={getDataPromise()} />Conditional Context Reading
Context in if StatementRead context only when specific condition met.
function Header({ showTheme }: Props) {
if (!showTheme) return <Text>Plain</Text>;
const theme = use(ThemeContext);
return <CardView color={theme.bg} />;
}Context in LoopCall use inside array iteration safely.
const configs = contexts.map((ctx) => use(ctx));Dynamic Context SelectionChoose 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
Build a responsive profile card that conditionally unwraps theme context and unhooks data promises using use().
- Accept external data promise as component prop.
- Conditionally read ThemeContext based on visual setting flag.
- Unwrap user data promise synchronously via the use API.
- Render user details within parent Suspense boundary.
const ThemeCtx = createContext({ dark: true });
export function Profile({
promise,
styled,
}: ProfileProps) {
let theme = null;
if (styled) {
theme = use(ThemeCtx);
}
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().