React API Reference: Hooks Reference

Provide a consolidated one-line reference table for React's full hook catalog, flag which hooks are newer/version-specific, and go deeper on a few less-commonly-covered ones: useId, useSyncExternalStore, and useDebugValue.

React ships eighteen built-in hooks. Most — useState, useReducer, useEffect, useRef, useContext, useMemo, useCallback, and the newer Action-related hooks — are covered in dedicated lessons elsewhere in this course; this lesson exists as a single consolidated reference table for all of them, plus a closer look at three hooks that don't get their own lesson: useId, useSyncExternalStore, and useDebugValue.

The full hook reference table

The table below lists every built-in React hook with a one-line description of its purpose. Hooks marked (newer) are version-specific APIs introduced in React 18 or 19 and are not available in earlier React versions — treat their presence in a codebase as a signal of the minimum React version required.

Grouping them by era helps: useState, useEffect, useContext, useReducer, useRef, useMemo, useCallback, useImperativeHandle, and useDebugValue have been part of React since hooks launched in React 16.8. useLayoutEffect also shipped at that time. Everything else below was added later.

• useState — declares a piece of local state and a setter function; the most basic hook. • useReducer — manages state via a reducer function and dispatched actions, useful for complex state transitions. • useEffect — runs a side effect after render and re-runs it when its dependencies change; supports a cleanup function. • useLayoutEffect — like useEffect, but fires synchronously after DOM mutations and before the browser paints; used for layout measurements. • useInsertionEffect (newer, React 18+) — fires before any DOM mutations, earlier than useLayoutEffect; intended for CSS-in-JS libraries injecting styles, not for application code. • useRef — holds a mutable value that persists across renders without causing a re-render when it changes; also used for DOM node references. • useImperativeHandle — customizes the instance value exposed to a parent when using ref forwarding, letting a component expose a controlled imperative API. • useContext — reads the current value of a Context from the nearest matching Provider above in the tree. • useMemo — memoizes the result of an expensive calculation between renders, recomputing only when its dependencies change. • useCallback — memoizes a function reference between renders, recomputing only when its dependencies change. • useDebugValue — displays a custom label for a custom hook's value in React DevTools; has no runtime effect on the app itself. • useId (newer, React 18+) — generates a stable, unique ID string for accessibility attributes, consistent between server and client renders. • useSyncExternalStore (newer, React 18+) — subscribes a component to an external (non-React) data store in a way that's safe for concurrent rendering. • useTransition (newer, React 18+) — marks a state update as a low-priority transition, returning an isPending flag while it's in progress. • useDeferredValue (newer, React 18+) — returns a deferred, lower-priority copy of a value that lags behind during rapid updates, letting urgent renders stay responsive. • useOptimistic (newer, React 19+) — shows a predicted state immediately while an async action is still pending, reverting to the real state once it settles. • useActionState (newer, React 19+) — wraps an action function, returning its result as state along with a pending flag, typically used with forms. • use (newer, React 19+) — reads the resolved value of a promise or the value of a Context, and unlike other hooks, can be called conditionally.

useId: stable unique identifiers

useId() (newer API, React 18+) generates a unique ID string that stays stable across a component's re-renders and, critically, matches between the server-rendered HTML and the client's hydration pass — something a naive Math.random() or an incrementing module-level counter cannot guarantee, since those can produce different values on the server versus the client and cause hydration mismatches.

Its primary use is linking form elements for accessibility, such as connecting a <label htmlFor={id}> to an <input id={id}>, or linking an input to its aria-describedby error message, in a component that might be rendered multiple times on the same page (so a hardcoded string ID would collide).

useId is explicitly documented as not being meant for generating keys in a list — list keys should come from stable data identifiers (like a database ID), not from useId, since useId's output is not guaranteed to be meaningful or stable in that context across list re-orderings.

useSyncExternalStore: safely subscribing to external state

useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?) (newer API, React 18+) is the correct way to read from a data source that lives outside React's own state system — a browser API like window.innerWidth, a third-party state management library's store, or a WebSocket-fed cache — while remaining correct under React's concurrent rendering, where a naive useEffect-based subscription can read a torn (inconsistent) snapshot during a concurrent render.

It takes a subscribe function (registers a callback for store changes and returns an unsubscribe function), a getSnapshot function (returns the current value, and must return a cached/stable reference when nothing has changed, not a fresh object every call), and an optional getServerSnapshot used during server rendering.

Most applications don't call this hook directly — it's primarily meant to be used inside library code (state management libraries, browser API wrapper hooks) that then exposes a normal-looking custom hook to consumers. Application code reaching for it directly is a sign it's integrating with something outside React's state model, like a vanilla JS class instance or browser API.

useId and useSyncExternalStore

Both hooks are React 18+; earlier React versions have no equivalent built-in API. getSnapshot in useSyncExternalStore must return the same reference when the underlying value hasn't changed, or the hook will think the store changed on every render.

// useId
function EmailField() {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>Email</label>
      <input id={id} type="email" />
    </>
  );
}

// useSyncExternalStore
function useWindowWidth(): number {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('resize', callback);
      return () => window.removeEventListener('resize', callback);
    },
    () => window.innerWidth,          // client snapshot
    () => 0                            // server snapshot (no window on the server)
  );
}

Code Examples

useDebugValue doesn't change what the hook returns or does at runtime — it only labels the hook's current value in the React DevTools component inspector, making a custom hook's internal state easier to read while debugging a tree that uses it many times. For expensive-to-compute debug labels, useDebugValue accepts a second argument — a formatting function that only runs when DevTools is actually open and inspecting that hook, avoiding the cost during normal renders.

useDebugValue(isOnline ? 'Online' : 'Offline');

Each PasswordField instance gets its own useId() value, so the label/input/aria-describedby linkage stays correct even with two copies of the component on the same page — a hardcoded id="password" string would have caused a duplicate-ID collision the second time.

Common mistake: Hardcoding a literal id string inside a reusable component works the first time it's rendered but silently breaks accessibility (and can break CSS/JS selectors) the moment the component is rendered more than once on a page.

function useOnlineStatus(): boolean {
  const isOnline = useSyncExternalStore(
    (cb) => {
      window.addEventListener('online', cb);
      window.addEventListener('offline', cb);
      return () => {
        window.removeEventListener('online', cb);
        window.removeEventListener('offline', cb);
      };
    },
    () => navigator.onLine,
    () => true
  );

  // Shows 'Online' or 'Offline' next to this hook's entry in React DevTools
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}
function PasswordField({ label }: { label: string }) {
  const id = useId();
  const hintId = `${id}-hint`;
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} type="password" aria-describedby={hintId} />
      <p id={hintId}>Must be at least 8 characters.</p>
    </div>
  );
}

// Rendering two on the same page never collides, even though
// neither instance was given an explicit id prop.
<>
  <PasswordField label="Password" />
  <PasswordField label="Confirm password" />
</>

Important Rules

useInsertionEffect, useId, useSyncExternalStore, useTransition, useDeferredValue, useOptimistic, useActionState, and use are all newer, version-specific hooks (React 18 or 19+) — check the target React version before using them.

getSnapshot passed to useSyncExternalStore must return a stable/cached reference when the store hasn't changed, or the component will appear to re-render in an infinite loop.

useDebugValue has no effect outside React DevTools and should never be relied on for actual application logic.

useId's output should be used for accessibility attribute linkage, not as React list keys.

Where this shows up

useId is common in any reusable form-field component library where accessibility linkage between labels and inputs matters. useSyncExternalStore underlies most third-party state management libraries' React bindings (so app code often gets its benefits indirectly through a library hook) and browser API wrappers for things like online status, media queries, or window size. useDebugValue is used mainly by shared custom-hook libraries to make their hooks' internal state legible in DevTools for consumers debugging their own trees.

function useStoreValue() { return useSyncExternalStore( subscribe, () => ({...store.getState() }) // new object every call); } function useStoreValue() { return useSyncExternalStore( subscribe, () => store.getState() // stable reference, only changes when the store actually updates); }

Spreading the state into a new object on every getSnapshot call means the snapshot is never reference-equal to the previous one, even when nothing changed, which makes React think the store is updating constantly and can trigger excessive re-renders or a 'getSnapshot should be cached' warning.

[multiple-choice] Why is useId preferred over Math.random() for generating an id used in server-rendered markup? A. Math.random() is slower at runtime B. useId guarantees the same value on the server and during client hydration, avoiding a mismatch C. useId generates shorter strings D. Math.random() cannot be used inside JSX

B. useId guarantees the same value on the server and during client hydration, avoiding a mismatch — useId is specifically designed to produce identical output between the server render and the client's hydration pass, which a random or incrementing counter value cannot reliably guarantee.

[true-false] useDebugValue changes what value a custom hook returns to its caller. A. True B. False

B. False — useDebugValue is purely a React DevTools labeling aid — it has no effect on the hook's actual return value or runtime behavior.

[multiple-choice] Which of these hooks is NOT newer/version-specific (i.e., has existed since hooks were first introduced)? A. useSyncExternalStore B. useTransition C. useContext D. useOptimistic

C. useContext — useContext has existed since React 16.8, when hooks first launched. useSyncExternalStore and useTransition are React 18+, and useOptimistic is React 19+.

Using the table above, name three hooks that are safe to use in a codebase still on React 17, and three that would require upgrading to at least React 18.

Safe on React 17: useState, useEffect, useContext (or useReducer, useRef, useMemo, useCallback — any pre-hooks-era hook). Requires React 18+: useId, useTransition, useSyncExternalStore (or useDeferredValue, useInsertionEffect) — and useOptimistic/useActionState/use would require React 19+ specifically.

Anything not marked (newer) in the reference table predates React 18; everything marked (newer, React 18+) or (newer, React 19+) does not.