Skip to the chapter
←ReactGGroundwork⚛React
React/Advanced/A6
39 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛A6 · Part 3 · Advanced

The use() hook

Reading a promise or a context during render — and the only hook that breaks the rules.

5 min read10 sections2 exercises
39 of 57

In this chapter

  • Reading a promise
  • The pattern it enables
  • Start requests together, read them apart
  • use or await?
  • It cannot go inside try/catch

and 5 more

about 5 min
10On this page
  1. Reading a promise
  2. The pattern it enables
  3. Start requests together, read them apart
  4. use or await?
  5. It cannot go inside try/catch
  6. Reading context
  7. Do not create the promise during render
  8. Caching promises in client code
  9. How it compares
  10. Where you will actually meet it

Reading a promise

function Comments({ commentsPromise }) {
  const comments = use(commentsPromise);      // suspends until it resolves
  return <ul>{comments.map(...)}</ul>;
}

<Suspense fallback={<Skeleton />}>
  <Comments commentsPromise={getComments()} />
</Suspense>

use unwraps a promise during render. If it is pending the component suspends and the nearest Suspense boundary shows its fallback; if it rejects, the nearest error boundary catches it. No useEffect, no loading flag, no error state in the component.

The pattern it enables

// server component — starts the request but does not await it
export default function Page() {
  const commentsPromise = getComments();     // no await
  return (
    <>
      <Article />                            // renders immediately
      <Suspense fallback={<Skeleton />}>
        <Comments commentsPromise={commentsPromise} />
      </Suspense>
    </>
  );
}

Awaiting in the server component would block the whole page on the slowest query. Passing the promise down lets the fast content render and stream immediately, while the slow part fills in later. This is the idiomatic way to avoid a server-side waterfall.

The value a server promise resolves to crosses the network, so it must be serialisable: plain objects, arrays, strings, numbers, dates — not class instances or functions.

Start requests together, read them apart

// ✗ a waterfall: the second request starts only after the first resolves
async function Page() {
  const user = await getUser();
  const posts = await getPosts();
}

// ✓ both start now; each part reveals when its own data lands
function Page() {
  const userPromise = getUser();
  const postsPromise = getPosts();
  return (
    <>
      <Suspense fallback={<HeaderSkeleton />}><Header userPromise={userPromise} /></Suspense>
      <Suspense fallback={<PostsSkeleton />}><Posts postsPromise={postsPromise} /></Suspense>
    </>
  );
}

Where a request starts and where it is read are separate decisions, and use is what lets you separate them. Two awaits in a row are sequential even when the requests are independent. Creating both promises first makes them parallel, and the boundaries decide what the user sees while they run. Two components can also read the same promise; it is fetched once and both resume when it settles.

use or await?

WhereUseWhy
Server Component, data this component rendersawaitServer Components can be async; it is simpler and needs no boundary of its own
Server Component, data a child renders laterpass the promiseStarts the request early without blocking this component
Client Componentuse(promise)Client components cannot be async functions

The rule of thumb: await where the data is needed to render the component you are in; hand over the promise where it is needed further down. Both can live in the same page.

The one hook that can be called conditionally
function Panel({ show, dataPromise }) {
  if (!show) return null;
  const data = use(dataPromise);      // ✓ legal — after an early return
}

Every other hook depends on stable call order. use does not store anything in the hook slot list, so it can sit inside a condition, a loop, or after a return. It still cannot be called from a callback or an effect — only while rendering a component or inside another hook.

It cannot go inside try/catch

// ✗ throws "Suspense Exception: This is not a real error!"
function Albums({ albumsPromise }) {
  try {
    const albums = use(albumsPromise);
  } catch (e) {
    return <p>Error</p>;
  }
}

// ✓ the boundary is the catch
<ErrorBoundary fallback={<p>Error</p>}>
  <Albums albumsPromise={albumsPromise} />
</ErrorBoundary>

Suspending works by interrupting the render, and a catch block would swallow that interruption. When a rejection should become a value rather than an error screen, handle it on the promise before passing it in:

const safe = getComments().catch(() => []);   // rejection becomes an empty list
<Comments commentsPromise={safe} />

Reading context

function Item({ compact }) {
  if (compact) {
    const theme = use(ThemeContext);   // ✓ conditional context read
    ...
  }
}

use(SomeContext) does what useContext does, with the same conditional freedom. Useful when a branch of a component needs context that the other branch has no business subscribing to. Like useContext, it looks for the closest provider above the calling component and ignores one rendered by that component itself. Reading context with use is not supported in Server Components.

Do not create the promise during render

function Comments({ id }) {
  const data = use(fetch("/api/comments/" + id));   // ✗ new promise every render
}

Suspending re-runs the component when the promise resolves. If the promise is created inside the render, the retry creates another one, which suspends again — an infinite loop of requests. React warns with "A component was suspended by an uncached promise". The promise must come from somewhere stable: a prop from a Server Component, a cache, or a framework API.

Caching promises in client code

const cache = new Map();

export function fetchComments(id) {
  if (!cache.has(id)) {
    cache.set(id, fetch("/api/comments/" + id).then((r) => r.json()));
  }
  return cache.get(id);                   // same promise for the same id
}

function Comments({ id }) {
  const comments = use(fetchComments(id)); // ✓ stable across retries
}

This is the smallest correct version, and seeing it explains why you usually want a library instead. This cache never expires, never refetches, keeps a rejected promise forever, and grows without limit. Query libraries and frameworks solve exactly those problems, and then hand use a stable promise.

On the server the equivalent is React's cache: wrap a data function with it, and every Server Component that calls it with the same arguments during one request shares a single call. It is scoped to that request and only works in Server Components, so it deduplicates without ever leaking one user's data into another's page.

Never peek at a promise's status or value to skip calling use. Always pass the promise and let React decide; bypassing it breaks Suspense optimisations and DevTools.

How it compares

use()Effect + stateQuery library
Loading statethe boundaryyou write itreturned to you
Errorserror boundaryyou write itreturned to you
Cachingnone of its ownnoneyes
Refetch, invalidatethe framework's jobmanualyes
Where it fitsserver-passed promiseslegacy, or one-offsclient-side data

use is not a replacement for a data library — it has no cache and no revalidation. It is the primitive that makes promises readable during render, which is what libraries and frameworks build on.

Where you will actually meet it

  • A Server Component starting a slow query and passing the promise to a client component.
  • Framework data APIs that hand you a promise instead of awaiting it for you.
  • Conditional context reads, occasionally.
  • use(browser()), added in React 19.3, to render a part only on the client — covered in rendering strategies.

In a plain client-side app with a query library, you will rarely call it directly — and that is fine. Knowing what it does explains how Suspense gets its promises.

Interview answer, one sentence

"use reads a promise or a context during render, suspending until it resolves, and it is the only hook that can be called conditionally because it stores nothing in the hook list. Its main use is passing an unawaited promise from a Server Component to a client one; the promise must be cached, errors go to an error boundary rather than try/catch, and inside a Server Component plain await is usually simpler."

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsSuspense with use(), and a promise cacheadvanced
  • 5 testsReading context only on one branchadvanced
All 54 for this level →
A6

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. A5Previous · A5Server Actions
  2. A6you are here
  3. Next · A7Rendering strategiesWhere the HTML is produced, and when — the decision that shapes everything downstream.6 min read

01On this page

10 sections · 0%

  1. 01Reading a promise
  2. 02The pattern it enables
  3. 03Start requests together, read them apart
  4. 04use or await?
  5. 05It cannot go inside try/catch
  6. 06Reading context
  7. 07Do not create the promise during render
  8. 08Caching promises in client code
  9. 09How it compares
  10. 10Where you will actually meet it

[] chapters · t top