use() & Data Fetching
The React 19 use() API
Section titled “The React 19 use() API”use() is a React 19 API that reads the value of a resource during render — most importantly a promise or a context. When you pass it a promise, the component suspends while the promise is pending (showing the nearest Suspense fallback) and returns the resolved value once it settles.
import { use, Suspense } from "react";
function Message({ messagePromise }) { const text = use(messagePromise); // suspends until the promise resolves return <p>{text}</p>;}
function Container({ messagePromise }) { return ( <Suspense fallback={<p>Loading…</p>}> <Message messagePromise={messagePromise} /> </Suspense> );}use() breaks one rule you learned for other hooks: it can be called conditionally — inside an if, or in a loop. That’s allowed precisely because it isn’t tied to the fixed hook slots the way useState/useEffect are.
The critical rule: cache the promise
Section titled “The critical rule: cache the promise”use() does not start the fetch, and it does not cache anything. It reads a promise you give it — so that promise must be stable across renders. Creating a promise inside the component body means a new promise every render, which suspends forever (or refetches endlessly).
// ❌ New promise every render — refetches / never settles.function Bad({ id }) { const data = use(fetch(`/api/${id}`).then(r => r.json())); // ...}
// ✅ The promise is created outside and passed in (cached by the caller/framework).function Good({ dataPromise }) { const data = use(dataPromise); // ...}This is why raw use() is almost always paired with a cache — either a framework’s data layer or a manual Map cache — that returns the same promise instance for the same request.
Render-as-you-fetch
Section titled “Render-as-you-fetch”The pattern use() enables is render-as-you-fetch: kick off the request before or as you render (e.g. in a route loader, or on hover), pass the promise down, and let the component read it via use() inside a Suspense boundary. Contrast the three approaches:
flowchart TB
subgraph old["fetch-on-render (useEffect)"]
r1["render"] --> e1["effect runs"] --> f1["then fetch starts"] --> r2["re-render with data"]
end
subgraph new["render-as-you-fetch (use + Suspense)"]
start["start fetch early
(loader / event)"] --> pass["pass promise down"]
pass --> read["use(promise) suspends
→ Suspense fallback → data"]
end use() also reads context, and unlike useContext it can be called conditionally — handy for reading context inside a branch.
You usually won’t write this by hand
Section titled “You usually won’t write this by hand”Here’s the honest part. Raw use() + Suspense + a hand-rolled promise cache is powerful but low-level. In real apps you almost always get render-as-you-fetch through a framework (a router/loader that fetches per route and provides the cache) or a data library like TanStack Query (which manages caching, revalidation, and Suspense integration for you). Learn use() to understand the mechanism; reach for a library to ship it.