Chapter 4 of 4

Lesser-Known Built-in Hooks

Three hooks that solve specific problems custom hooks often need.

useId: stable unique ids

Accessible markup needs ids that link labels to inputs. useId generates one that matches between the server render and the client, which Math.random() cannot do.

function PasswordField() {
  const id = useId();

  return (
    <>
      <label htmlFor={id}>Password</label>
      <input id={id} type="password" aria-describedby={id + "-hint"} />
      <p id={id + "-hint"}>At least 8 characters</p>
    </>
  );
}

useDebugValue: labelling hooks in DevTools

React DevTools shows the label next to the hook. It has no effect in production.
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);
  useDebugValue(isOnline ? "Online" : "Offline");
  return isOnline;
}

useSyncExternalStore: subscribing to non-React state

When a value lives outside React - a browser API, a third-party store, an event emitter - this hook subscribes to it safely, including during concurrent rendering.

function useOnlineStatus() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener("online", callback);
      window.addEventListener("offline", callback);
      return () => {
        window.removeEventListener("online", callback);
        window.removeEventListener("offline", callback);
      };
    },
    () => navigator.onLine,        // value on the client
    () => true                     // value during server rendering
  );
}

Write a debounce helper

Complete debounce so the returned function only calls fn after the given delay, restarting the timer on each call.