Advanced React Hooks šŸš€

1. Introduction šŸ‘‹

Beyond the everyday Hooks like useState and useEffect, React provides a set of advanced Hooks designed for specialized scenarios — imperative APIs, concurrent rendering, external store synchronization, and optimistic UI updates. This tutorial covers each of these in depth, along with the concurrent features they enable.

Information

This tutorial assumes strong familiarity with useState, useEffect, useRef, and Custom Hooks, covered in earlier tutorials.

2. useLayoutEffect šŸ“

useLayoutEffect fires synchronously after React updates the DOM, but before the browser paints the screen — unlike useEffect, which fires after painting.

Code Snippet

function Tooltip({ targetRef }) {
  const tooltipRef = useRef(null);

  useLayoutEffect(() => {
    const { bottom } = targetRef.current.getBoundingClientRect();
    tooltipRef.current.style.top = `${bottom}px`;
  }, [targetRef]);

  return <div ref={tooltipRef} className="tooltip">Tip</div>;
}

Warning

useLayoutEffect blocks the browser from painting until it finishes, so overusing it can hurt perceived performance. Reach for it only when you must measure or mutate the DOM before the user sees a flicker.

3. useImperativeHandle šŸŽ›ļø

useImperativeHandle customizes what a parent component receives when it attaches a ref to a child, exposing a curated imperative API instead of the raw DOM node.

Code Snippet

import { forwardRef, useImperativeHandle, useRef } from 'react';

const VideoPlayer = forwardRef((props, ref) => {
  const videoRef = useRef(null);

  useImperativeHandle(ref, () => ({
    play: () => videoRef.current.play(),
    pause: () => videoRef.current.pause(),
  }));

  return <video ref={videoRef} src={props.src} />;
});

function App() {
  const playerRef = useRef(null);
  return (
    <>
      <VideoPlayer ref={playerRef} src="/movie.mp4" />
      <button onClick={() => playerRef.current.play()}>Play</button>
    </>
  );
}

Tip

Use this Hook to expose a minimal, intentional API rather than the entire DOM node — it keeps the child component's internals properly encapsulated.

4. useId šŸ†”

useId generates a unique, stable identifier string, consistent between server and client renders — ideal for linking form labels and inputs via accessibility attributes.

Code Snippet

function FormField({ label }) {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </>
  );
}

Important

Never use useId to generate keys for a list — it's designed for accessibility attributes, not for identifying items in dynamic collections.

5. useTransition 🌊

useTransition lets you mark a state update as a low-priority transition, keeping the UI responsive to more urgent updates (like typing) while the transition completes in the background.

Code Snippet

function SearchPage() {
  const [query, setQuery] = useState("");
  const [isPending, startTransition] = useTransition();
  const [results, setResults] = useState([]);

  function handleChange(e) {
    const value = e.target.value;
    setQuery(value); // urgent — updates immediately

    startTransition(() => {
      setResults(computeExpensiveResults(value)); // low priority
    });
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <span>Updating results...</span>}
      <ResultsList results={results} />
    </>
  );
}

Tip

isPending lets you show a subtle loading indicator without blocking the input field from updating instantly as the user types.

6. useDeferredValue ā³

useDeferredValue achieves a similar goal to useTransition, but is applied directly to a value rather than wrapping a state-setting function.

Code Snippet

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  const results = useMemo(() => computeExpensiveResults(deferredQuery), [deferredQuery]);

  return <ResultsList results={results} />;
}

Note

React renders with the old deferred value first, then re-renders with the new one once ready — useful when you don't control the state update itself (e.g., a value coming from props).

7. useSyncExternalStore šŸ”Œ

useSyncExternalStore safely subscribes a component to an external data source outside of React's own state model — such as browser APIs or third-party stores — while remaining compatible with concurrent rendering.

Code Snippet

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

Important

Manually subscribing to external stores with useEffect and useState can produce visual tearing under concurrent rendering — useSyncExternalStore exists specifically to prevent that.

8. useInsertionEffect šŸ’‰

useInsertionEffect runs before any DOM mutations, even earlier than useLayoutEffect. It's designed almost exclusively for CSS-in-JS library authors who need to inject <style> tags before layout is measured.

Code Snippet

function useCSS(rule) {
  useInsertionEffect(() => {
    const styleTag = document.createElement("style");
    styleTag.textContent = rule;
    document.head.appendChild(styleTag);
    return () => document.head.removeChild(styleTag);
  }, [rule]);
}

Caution

Application developers rarely need this Hook directly — it exists primarily for the internals of CSS-in-JS libraries like styled-components or emotion.

9. useOptimistic ✨

useOptimistic lets you show an optimistic UI state immediately, before an asynchronous action actually completes, then automatically reconciles once the real result arrives.

Code Snippet

function LikeButton({ postId, likes }) {
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (current) => current + 1
  );

  async function handleLike() {
    addOptimisticLike();
    await likePost(postId); // real server request
  }

  return <button onClick={handleLike}>ā¤ļø {optimisticLikes}</button>;
}

Tip

If the underlying async action fails, the optimistic value automatically reverts once the real state updates — you don't need to manually roll it back.

10. useActionState šŸ“

useActionState manages state driven by a form Action, tracking the pending status and result of the action automatically.

Code Snippet

async function subscribeAction(previousState, formData) {
  const email = formData.get("email");
  if (!email.includes("@")) {
    return { error: "Invalid email address" };
  }
  await subscribeUser(email);
  return { success: true };
}

function SubscribeForm() {
  const [state, formAction, isPending] = useActionState(subscribeAction, {});

  return (
    <form action={formAction}>
      <input name="email" type="email" />
      <button disabled={isPending}>{isPending ? "Submitting..." : "Subscribe"}</button>
      {state.error && <p className="error">{state.error}</p>}
      {state.success && <p>Subscribed! āœ…</p>}
    </form>
  );
}

11. use šŸŽÆ

use is a unique Hook that can unwrap a Promise or read Context, and — unusually — can be called conditionally, unlike other Hooks.

Code Snippet

function Comments({ commentsPromise }) {
  const comments = use(commentsPromise); // suspends until resolved
  return (
    <ul>
      {comments.map((c) => <li key={c.id}>{c.text}</li>)}
    </ul>
  );
}

function Wrapper({ commentsPromise }) {
  return (
    <Suspense fallback={<p>Loading comments...</p>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  );
}

Important

When use unwraps a Promise, the component suspends — it must be wrapped in a Suspense boundary to display a fallback while waiting.

12. useDebugValue šŸ·ļø

useDebugValue displays a custom, human-readable label for a custom Hook when inspected in React Developer Tools — purely a development aid with no runtime effect.

Code Snippet

function useOnlineStatus() {
  const isOnline = useSyncExternalStore(subscribe, getSnapshot);
  useDebugValue(isOnline ? "Online" : "Offline");
  return isOnline;
}

Note

This Hook is purely for debugging visibility — it doesn't affect your component's behavior or output in any way.

13. useMemoCache (Experimental) 🧪

useMemoCache is an internal, compiler-generated Hook used by the React Compiler to automatically memoize values — it's not intended to be called directly by application code.

Caution

You should never write useMemoCache calls by hand. If you see it in compiled output, it means the React Compiler is automatically optimizing your component — no manual useMemo/useCallback required for that logic.

14. Understanding Concurrent Features 🌊

Concurrent React allows React to prepare multiple versions of the UI simultaneously, interrupt low-priority rendering work to handle urgent updates, and avoid blocking the main thread during expensive renders.

Concurrent Rendering
Urgent Updates
Transitions
Deferred Values
Typing, clicking — rendered immediately
Marked via useTransition — can be interrupted
Marked via useDeferredValue — computed when idle

15. Optimistic UI ✨

Optimistic UI updates the interface immediately in response to a user action, assuming success, rather than waiting for a server round-trip before reflecting the change.

Code Snippet

function TodoItem({ todo, onToggle }) {
  const [optimisticDone, setOptimisticDone] = useOptimistic(todo.done);

  async function handleToggle() {
    setOptimisticDone(!optimisticDone);
    await onToggle(todo.id); // actual server update
  }

  return (
    <li onClick={handleToggle} style={{ opacity: optimisticDone ? 0.5 : 1 }}>
      {todo.text}
    </li>
  );
}

Tip

Optimistic UI dramatically improves perceived performance for actions like likes, toggles, and comments, where failures are rare and easily recoverable.

16. Transitions šŸ”€

A transition is a state update explicitly marked as non-urgent, allowing React to interrupt it if a more urgent update (like a keystroke) comes in.

Code Snippet

function TabContainer() {
  const [tab, setTab] = useState("home");
  const [isPending, startTransition] = useTransition();

  function selectTab(nextTab) {
    startTransition(() => setTab(nextTab));
  }

  return (
    <div style={{ opacity: isPending ? 0.6 : 1 }}>
      <TabPanel tab={tab} />
    </div>
  );
}

17. Deferred Rendering ā³

Deferred rendering, via useDeferredValue, lets an expensive part of the UI lag slightly behind a fast-changing input value, keeping the input itself perfectly responsive.

Code Snippet

function App() {
  const [text, setText] = useState("");
  const deferredText = useDeferredValue(text);
  const isStale = text !== deferredText;

  return (
    <>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <div style={{ opacity: isStale ? 0.5 : 1 }}>
        <ExpensiveList query={deferredText} />
      </div>
    </>
  );
}

18. External Store Synchronization šŸ”—

useSyncExternalStore is the recommended way to connect React components to non-React state sources like browser APIs, third-party state libraries, or custom event emitters.

Code Snippet

function useWindowWidth() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener("resize", callback);
      return () => window.removeEventListener("resize", callback);
    },
    () => window.innerWidth
  );
}

19. Imperative APIs šŸŽ›ļø

Combining forwardRef with useImperativeHandle lets you design components with a controlled, minimal imperative surface, useful for things like modals, video players, or animation triggers.

Code Snippet

const Modal = forwardRef((props, ref) => {
  const [isOpen, setIsOpen] = useState(false);

  useImperativeHandle(ref, () => ({
    open: () => setIsOpen(true),
    close: () => setIsOpen(false),
  }));

  return isOpen ? <div className="modal">{props.children}</div> : null;
});

20. Custom Debugging Hooks šŸ›

Code Snippet

function useFetchStatus(url) {
  const { data, error, isLoading } = useFetch(url);

  useDebugValue(
    isLoading ? "Loading..." : error ? `Error: ${error.message}` : "Loaded"
  );

  return { data, error, isLoading };
}

Tip

Adding useDebugValue to shared custom Hooks used across a large codebase makes debugging significantly easier for the whole team in React DevTools.

21. Hook Composition 🧬

Advanced Hooks are frequently combined to build sophisticated, production-ready patterns — for example, pairing useTransition with useOptimistic for a smooth, responsive form submission experience.

Code Snippet

function CommentForm({ onSubmit, comments }) {
  const [optimisticComments, addOptimistic] = useOptimistic(comments,
    (state, newComment) => [...state, newComment]
  );
  const [isPending, startTransition] = useTransition();

  function handleSubmit(text) {
    startTransition(async () => {
      addOptimistic({ id: "temp", text, pending: true });
      await onSubmit(text);
    });
  }

  return <CommentList comments={optimisticComments} isPending={isPending} />;
}

22. Performance Considerations ⚔

  • useLayoutEffect and useInsertionEffect block painting — use them sparingly and only when timing truly matters.
  • useTransition and useDeferredValue improve perceived performance but don't reduce the actual computation cost.
  • useSyncExternalStore should return a stable snapshot to avoid unnecessary re-renders — avoid creating new objects on every call.

Warning

A getSnapshot function passed to useSyncExternalStore that returns a new object every call (even with identical data) causes the component to re-render on every check, defeating its purpose.

23. TypeScript with Advanced Hooks šŸ”·

Code Snippet

interface FormState {
  error?: string;
  success?: boolean;
}

const [state, formAction, isPending] = useActionState<FormState, FormData>(
  async (prevState, formData) => {
    // ...
    return { success: true };
  },
  {}
);

// useOptimistic with explicit types
const [optimisticLikes, addOptimisticLike] = useOptimistic<number, void>(
  likes,
  (current) => current + 1
);

Tip

Most advanced Hooks infer their types well from usage, but explicit generics help clarify intent in useActionState and useOptimistic when the shapes involved are non-trivial.

24. Best Practices 🌟

  1. Reach for useTransition/useDeferredValue only when a measured responsiveness issue exists.
  2. Use useLayoutEffect sparingly — prefer useEffect unless DOM measurement before paint is truly required.
  3. Wrap use() Promise consumers in a Suspense boundary with a meaningful fallback.
  4. Keep useImperativeHandle APIs minimal and intentional — avoid exposing the entire DOM node.
  5. Always handle the failure case when using useOptimistic for actions that can genuinely fail.

25. Common Mistakes 🚫

  • Reaching for useLayoutEffect by default instead of useEffect, unnecessarily blocking paint.
  • Using use() to unwrap a Promise without a surrounding Suspense boundary.
  • Treating useTransition as a way to make computations faster, rather than just deprioritizing them.
  • Returning unstable snapshots from useSyncExternalStore, causing excessive re-renders.
  • Overusing useImperativeHandle where simple prop-driven, declarative patterns would work just as well.

Danger

Calling use() with a new Promise created on every render (instead of a stable, cached one) causes the component to suspend repeatedly in an infinite loop.

26. Frequently Asked Questions ā“

Question

When should I reach for these advanced Hooks instead of the basics?

Answer

Only when you hit a specific, identified need — a genuinely janky input during heavy computation, a third-party library requiring an imperative API, or synchronizing with a truly external store. They're specialized tools, not everyday defaults.

Question

Is useOptimistic safe for critical, high-stakes actions?

Answer

It works best for low-risk, easily reversible actions like likes or toggles. For critical operations (like payments), waiting for real confirmation is usually the safer choice.

Question

Do I need to learn all of these Hooks to use React effectively?

Answer

No. Most applications rely primarily on useState, useEffect, and useContext — these advanced Hooks solve specific, less common problems as they arise.

27. Summary šŸ“

React's advanced Hooks unlock specialized capabilities — imperative APIs, concurrent rendering, external store synchronization, and optimistic UI — that go beyond everyday component logic. While most components never need them, understanding useTransition, useDeferredValue, useOptimistic, and their peers equips you to solve performance and UX challenges that basic Hooks can't address alone.

Summary

With advanced Hooks covered, strong next steps include exploring Suspense and Error Boundaries in depth, along with Server Components, which frequently work hand-in-hand with several of the Hooks introduced here.