The use() Hook: Conditional Promise and Context Unwrapping
- Last updated
- Prerequisites:
- React Suspense
- React Context API
- Promises
- react
- use-hook
- promises
- context
- suspense
- react-19
Read at your depth
The practical view
The `use()` API in React 19 lets you read the value of a resource like a Promise or a React Context. Unlike all other React hooks (`useState`, `useEffect`, `useContext`), `use()` **can be called conditionally** inside `if` statements, loops, and after early returns. When passed a Promise, `use(promise)` suspends the component until the promise resolves, integrating seamlessly with `<Suspense>`.
The same idea in other frameworks
`const data = use(promise);` or `const theme = use(ThemeContext);`Angular `resource()` or `inject(Token)` inside functional contexts.`await` inside `<script setup>` with root `<Suspense>`.Svelte `{#await promise then data}` block unwrapping.Legacy vs modern
useEffect Data Fetching vs use() Promise Unwrapping
`useEffect` requires boilerplate loading states and causes waterfalls; `use()` unwraps promises directly under Suspense.
useEffect(() => { fetch().then(setData); }, []); // ❌ Boilerplate + client waterfallconst user = use(userPromise); // ✅ NEW: Direct synchronous unwrap under SuspenseInterview gotchas
Context
Interviewers test your deep understanding of how React Suspense uses thrown promises under the hood.
The mechanical answer
No, you should **not** wrap `use(promise)` inside a standard JavaScript `try/catch` block. When a promise is pending, `use()` intentionally *throws the promise* to signal the nearest `<Suspense>` boundary. If your `try/catch` intercepts the thrown promise, Suspense will fail to catch it, causing the component to break.
Trap
To catch promise rejections from `use()`, use an **Error Boundary** or pass an already-caught promise with fallback data.
Context
Checks awareness of promise caching and infinite re-render loops.
The mechanical answer
Passing an inline promise created in the render body (`use(fetch(...))`) causes an **infinite render loop**. Every render creates a new un-cached Promise instance, which suspends, resolves, triggers a re-render, creates another new Promise, and repeats forever. Promises passed to `use()` must be created outside render, cached via React `cache()`, or passed as props from a Server Component.
Trap
Always pass cached or stable promises to `use()`.