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
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
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
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
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
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
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
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
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
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
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
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
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.
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
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
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
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
24. Best Practices š
- Reach for useTransition/useDeferredValue only when a measured responsiveness issue exists.
- Use useLayoutEffect sparingly ā prefer useEffect unless DOM measurement before paint is truly required.
- Wrap use() Promise consumers in a Suspense boundary with a meaningful fallback.
- Keep useImperativeHandle APIs minimal and intentional ā avoid exposing the entire DOM node.
- 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
26. Frequently Asked Questions ā
Question
Answer
Question
Answer
Question
Answer
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.