Other Hooks
Less-common hooks: useLayoutEffect, useId, useSyncExternalStore, useImperativeHandle, and useDebugValue.
The core idea#
Beyond useState/useEffect/useMemo/useCallback, React ships specialized hooks for DOM measurement, stable IDs, external stores, imperative handles, and devtools labels. Knowing when each applies is a common senior signal.
useLayoutEffect vs useEffect#
useLayoutEffect fires synchronously after DOM mutations but before the browser paints — use it to measure layout and mutate the DOM without a visible flicker. useEffect fires after paint, asynchronously.
function Tooltip({ targetRef }) {
const [pos, setPos] = useState({ top: 0 });
useLayoutEffect(() => {
const rect = targetRef.current.getBoundingClientRect(); // measure
setPos({ top: rect.bottom }); // reposition before paint
}, []);
return <div style={{ top: pos.top }} />;
}
⚠️ useLayoutEffect blocks paint — overuse hurts performance, and it warns during SSR (no DOM). Default to useEffect; reach for useLayoutEffect only to prevent a visual flash.
useId#
Generates a stable, SSR-safe unique id consistent across server and client — for linking label/input, aria-describedby, etc. Not for list keys.
const id = useId();
<label htmlFor={id}>Email</label>
<input id={id} />
useSyncExternalStore#
Subscribes to an external store (Redux, Zustand, a browser API) in a concurrent-safe way, avoiding tearing. Store libraries use it internally; you use it directly to wrap browser state.
function useOnlineStatus() {
return useSyncExternalStore(
(cb) => { window.addEventListener("online", cb);
window.addEventListener("offline", cb);
return () => { /* cleanup */ }; },
() => navigator.onLine, // client snapshot
() => true // server snapshot
);
}
useImperativeHandle (+ forwardRef)#
Customizes the ref value a parent receives, exposing a narrow imperative API instead of the raw DOM node.
const Input = forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus(),
clear: () => { inputRef.current.value = ""; },
}));
return <input ref={inputRef} {...props} />;
});
// parent: inputRef.current.focus()
🟢 React 19 lets you pass ref as a regular prop, so many forwardRef wrappers become unnecessary — but useImperativeHandle is still how you expose a custom handle.
useDebugValue#
Labels a custom hook in React DevTools — no runtime effect, dev-only.
useDebugValue(isOnline ? "Online" : "Offline");
When to use each#
| Hook | Use when |
|---|---|
useLayoutEffect |
Measure/mutate DOM before paint to avoid flicker |
useEffect |
Everything else — data, subscriptions, side effects (default) |
useId |
Generate SSR-stable ids for accessibility attributes |
useSyncExternalStore |
Subscribe to a store/browser API without tearing |
useImperativeHandle |
Expose a limited imperative API via ref |
useDebugValue |
Label a reusable custom hook in DevTools |
flowchart TD
A["Effect scheduled"] --> B{"Need DOM measurement<br/>before paint?"}
B -->|"Yes"| C["useLayoutEffect — sync, blocks paint"]
B -->|"No"| D["useEffect — async, after paint"]
Interview Q&A#
Q1. useLayoutEffect vs useEffect — timing and use case?
useLayoutEffect runs synchronously after DOM mutation but before paint, so you can measure and reposition without flicker (tooltips, autosizing). useEffect runs after paint, asynchronously — the default for data fetching and subscriptions since it doesn't block rendering.
Q2. Why does useId exist instead of Math.random()?
Random ids differ between server and client, causing hydration mismatches. useId produces a deterministic id stable across SSR and the client, safe for htmlFor/aria-*. It is not meant for list keys.
Q3. What problem does useSyncExternalStore solve?
Concurrent rendering can "tear" — different components reading an external store mid-render see inconsistent values. useSyncExternalStore gives React a subscribe function and a snapshot getter so all reads stay consistent, plus an SSR snapshot. Store libraries build on it.
Q4. When would you reach for useImperativeHandle?
When a parent legitimately needs to call methods on a child (focus, scroll, play/pause a media element). Combined with forwardRef, it exposes a curated API instead of the raw DOM node. Prefer props/state first; use it only for genuinely imperative actions.
Q5. Does useDebugValue affect production? No. It only sets a label shown in React DevTools for custom hooks. React may skip evaluating its (optional) formatting function unless DevTools is open.