Use React's use hook for promises and context
Published July 6, 2027
import { use, Suspense, createContext } from "react";
const ThemeContext = createContext("light");
const greetingCache = new Map<string, Promise<string>>();
const getGreeting = (name: string) => {
if (!greetingCache.has(name)) {
greetingCache.set(name, fetch(`/api/greet/${name}`).then((res) => res.text()));
}
return greetingCache.get(name)!;
};
const Greeting = ({ name }: { name: string }) => {
const message = use(getGreeting(name));
const theme = use(ThemeContext);
return <p className={theme}>{message}</p>;
};
export const GreetingPage = ({ name }: { name: string }) => (
<Suspense fallback={<p>Loading…</p>}>
<Greeting name={name} />
</Suspense>
);What
use(promise) suspends the calling component until the promise resolves and returns its resolved value. use(context) reads a Context value, the same value useContext would return. GreetingPage below wires both into one render: Greeting reads a cached promise and a theme context in the same component.
Why it matters
Before use, reading a promise during render wasn't possible at all — data fetching meant useEffect + useState (fetch after mount, an extra render before data shows) or a library that hid its own Suspense wiring behind a hook. use(promise) lets a component suspend exactly where it reads the data, integrating with a wrapping <Suspense> the same way React.lazy already did for component code.
use(context) is the more surprising half. React's own docs are explicit that use is not a Hook — it doesn't follow the rules of hooks, and can be called conditionally, inside loops, or after an early return, which useContext can never do. That makes it useful in places useContext structurally can't reach, like a context read that's only relevant inside one branch of an if.
How it works
greetingCache/getGreetingmemoize the promise pername, so the samePromiseinstance is returned across re-renders. This isn't optional: passing a freshly created promise touseon every render (use(fetch(...))inline) means each render suspends on a promise that never has a chance to resolve before the next render starts a new one.Greetingcallsuse(getGreeting(name))first — this is where the component actually suspends — thenuse(ThemeContext), reading context the same render.GreetingPagewrapsGreetingin<Suspense fallback={...}>. A production version would also wrap it in an Error Boundary, since a rejected promise propagates there, not to atry/catcharounduse.
Gotchas
- Never construct the promise inline inside
use(...)during render — it has to come from somewhere stable (a cache, a prop, a ref), or the component suspends forever, re-fetching on every attempt. use(promise)can't be wrapped intry/catch. A rejected promise is handled by the nearest Error Boundary, exactly like a thrown error during render.- "Not a Hook" only means
useis exempt from the conditional-call restriction — it can still only be called during the render of a Component or another Hook, never inside an event handler or auseEffectcallback.
Related
useSyncExternalStore— for state that lives outside React and changes over time via subscription;use(promise)is for a single resolution, not an ongoing stream of updates.React.lazy— the original special case of suspending on a promise, scoped to loading a component definition;usegeneralizes the same mechanism to any promise.