Trust is earned, not given

A different perspective

2023-06-20 · Projects

React.js, part 5: the use() hook and reading a promise in render

Part 5from the React.js series · 20 parts in all

For years the rule was absolute: data fetching belongs in an effect, render must be pure. The use hook bends that rule in a controlled way — it lets a client component read a promise (or a context) during render, and cooperates with Suspense instead of guessing. Part 5 covers what it does, and the discipline it still requires.

use(promise): unwrap a value the server already started

use suspends the component until the promise resolves, then returns its value. The promise must be stable across renders — a new promise every render is an infinite loop — so it is normally created by a server component or a cache and passed down:

'use client';

import { use } from 'react';

export function Comments({ commentsPromise }) {
  // Reading the promise is a render-time unwrap, not an effect.
  // While it is pending, the nearest <Suspense> shows its fallback.
  const comments = use(commentsPromise);
  return <ul>{comments.map(c => <li key={c.id}>{c.text}</li>)}</ul>;
}
// Server component: start the promise, hand the CLIENT the promise itself.
export default function Article({ id }) {
  const commentsPromise = getComments(id);   // do not await
  return (
    <Suspense fallback={<Spinner />}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  );
}

Notice the shape: the server starts the work early, the client unwraps it where it is needed. That is how RSC streams data without a client-side fetch at all.

use(context): context without the hook caveats

use also reads context, and unlike useContext it may be called conditionally — inside an if or after an early return — which finally makes "optional provider" patterns comfortable:

function Themed({ invert }) {
  if (!invert) return <Plain />;
  const theme = use(ThemeContext);   // legal: use() is not a hook-position API
  return <div className={theme}>...</div>;
}

What use() does not change

Components still must be pure; use is a read, not a fetch trigger. Creating the promise is still someone else's job — a server component, a route loader, or a cache keyed by the request. The mental shift is that "loading state" is expressed as a Suspense boundary, not an isLoading boolean. Next: pulling external, non-React data into React safely with useSyncExternalStore.