Irbisa · Cheatsheet

Hooks in Depth

Rules of hooks, custom hooks, useReducer, useContext, useMemo and useCallback, useId, useSyncExternalStore, useTransition, use

Middle Developer · 19 questions

  1. 01

    What are the rules of hooks, and why does React need them?

    Call hooks only at the top level of a React function component or a custom hook, never inside conditions, loops, nested functions or after an early return.

  2. 02

    What is a custom hook, and when should you extract one?

    A custom hook is a function whose name starts with use and that calls other hooks.

    function useDebouncedValue<T>(value: T, delayMs: number): T {  const [debounced, setDebounced] = useState(value);   useEffect(() => {    const id = setTimeout(() => setDebounced(value), delayMs);    return () => clearTimeout(id);…
  3. 03

    What problem does useContext solve, and how do you provide and read a context?

    useContext lets any component read a value from the nearest provider above it without passing the value through every intermediate component as props (prop drilling).

    type Theme = 'light' | 'dark';const ThemeContext = createContext<Theme>('light'); function App() {  const [theme, setTheme] = useState<Theme>('dark');  return (…
  4. 04

    When would you use useReducer instead of useState?

    Use useReducer when the next state depends on the previous one through several kinds of updates, or when several fields change together: all the transitions live in one pure reducer(state, action) function instead of being spread across event handlers.

    type State = { items: string[]; draft: string };type Action =  | { type: 'typed'; text: string }  | { type: 'added' }  | { type: 'removed'; index: number };…
  5. 05

    What is the difference between useMemo and React.memo?

    React.memo wraps a component and skips re-rendering it when its props equal the previous ones.

    // React.memo: wraps a component, skips its render when props are equalconst Row = memo(function Row({ item }: { item: Item }) {  return <li>{item.name}</li>;}); function ProductList({ items, query }: { items: Item[]; query: string }) {…
  6. 06

    How does React know which useState call returns which state if hooks have no names or keys?

    By call order.

  7. 07

    What is the difference between useMemo and useCallback, and when are they worth using?

    useMemo(fn, deps) caches the result of calling fn; useCallback(fn, deps) caches the function itself, and is the same as useMemo(() => fn, deps).

    const Chart = memo(function Chart({ points, onSelect }: ChartProps) {  /* expensive SVG rendering */}); function Dashboard({ rows, filter }: { rows: Row[]; filter: string }) {  const [selected, setSelected] = useState<string | null>(null);…
  8. 08

    Why does every consumer of a context re-render when its value changes, and how do you limit those re-renders?

    When a provider receives a new value (compared with Object.is), React re-renders every component that reads that context, even if it uses only one field, and memo on the consumer does not stop it.

  9. 09

    Find the bug: the profile works for guests, but after signing in the page crashes with "Rendered more hooks than during the previous render".

    function Profile() {  const user = useContext(UserContext);  if (!user) return <SignInPrompt />;   const [orders, setOrders] = useState<Order[]>([]);  useEffect(() => {    fetchOrders(user.id).then(setOrders);  }, [user.id]);   return <OrderList user={user} orders={orders} />;}

    The early return sits above useState and useEffect, so a guest render calls no hooks after it and a signed-in render calls two more.

  10. 10

    What does this log when the user clicks "+1" three times and then "Log"?

    function Counter() {  const [count, setCount] = useState(0);   const log = useCallback(() => {    console.log(count);  }, []);   return (    <>      <button onClick={() => setCount(count + 1)}>+1</button>      <button onClick={log}>Log</button>    </>  );}

    It logs 0. useCallback with an empty dependency array keeps the function from the first render, and that function closed over count as it was then, so it never sees later values (a stale closure).

  11. 11

    How do you combine useReducer and context to manage a feature's state, and where does this pattern stop scaling?

    Keep the state in a useReducer inside a provider component, expose state and dispatch through two contexts, and wrap the reads in small custom hooks.

    const CartContext = createContext<CartState | null>(null);const CartDispatchContext = createContext<Dispatch<CartAction> | null>(null); export function CartProvider({ children }: { children: React.ReactNode }) {  const [cart, dispatch] = useReducer(cartReducer, { lines: [] });  return (…
  12. 12

    What is useId for, and why not generate ids with Math.random() or a module counter?

    useId() returns a unique string id that is stable for the lifetime of the component and identical on the server and the client, so it is safe for linking a <label> to an <input> or an aria-describedby to a hint in server-rendered HTML.

    function EmailField() {  const id = useId();  return (    <div>      <label htmlFor={`${id}-email`}>Email</label>      <input id={`${id}-email`} type="email" aria-describedby={`${id}-hint`} />…
  13. 13

    What do useTransition and useDeferredValue do, and when do you pick each?

    Both mark an update as non-urgent so React can render it in the background and interrupt it for urgent input like typing.

    function Search({ items }: { items: Item[] }) {  const [query, setQuery] = useState('');  const deferredQuery = useDeferredValue(query); // lags behind while rendering  const isStale = query !== deferredQuery;   return (…
  14. 14

    What is the use API in React 19, and how does it differ from other hooks?

    use(resource) reads a promise or a context during render.

    // PostPage.tsx, a Server Component: starts the request, does not await itexport default function PostPage({ postId }: { postId: string }) {  const commentsPromise = fetchComments(postId);  return (    <Suspense fallback={<Spinner />}>      <Comments commentsPromise={commentsPromise} />…
  15. 15

    How would you write a useDebounce hook for a search input?

    Restart a timer on every change and act only when it fires after a pause.

    // debounced value: the usual hook for a search inputfunction useDebouncedValue<T>(value: T, delayMs: number): T {  const [debounced, setDebounced] = useState(value);  useEffect(() => {    const timer = setTimeout(() => setDebounced(value), delayMs);    return () => clearTimeout(timer); // the next keystroke cancels the pending update…
  16. 16

    How would you write a useLocalStorage hook, and how do you keep it in sync across tabs and safe during server rendering?

    Treat localStorage as an external store and read it with useSyncExternalStore: subscribe to the storage event for writes from other tabs, fire your own event for writes in this tab, and return null from the server snapshot so server rendering never touches window.

    function subscribe(callback: () => void) {  window.addEventListener('storage', callback);       // writes from other tabs  window.addEventListener('local-storage', callback); // writes from this tab  return () => {    window.removeEventListener('storage', callback);    window.removeEventListener('local-storage', callback);…
  17. 17

    What is useSyncExternalStore, and why should external stores use it instead of useState plus useEffect?

    useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?) subscribes a component to a store that lives outside React (a browser API, a module-level store) and reads it in a way that is safe under concurrent rendering.

    function subscribe(callback: () => void) {  window.addEventListener('online', callback);  window.addEventListener('offline', callback);  return () => {    window.removeEventListener('online', callback);    window.removeEventListener('offline', callback);…
  18. 18

    Find the bug: the cart badge freezes the tab with "The result of getSnapshot should be cached to avoid an infinite loop".

    function useCartSummary() {  return useSyncExternalStore(cartStore.subscribe, () => ({    count: cartStore.getState().items.length,    total: cartStore.getState().total,  }));} function CartBadge() {  const { count, total } = useCartSummary();  return <span>{count} items, ${total}</span>;}

    getSnapshot builds a new object on every call, so React sees a different snapshot each time it checks, decides the store changed, re-renders, and checks again forever.

  19. 19

    How do you design the API of a reusable custom hook so it is easy to use and does not cause extra renders?

    Decide what the hook owns, return the smallest useful surface in a stable shape, and keep everything it returns referentially stable unless its meaning changed.

    interface UseDisclosureOptions {  defaultOpen?: boolean;  onOpenChange?: (open: boolean) => void;} export function useDisclosure({ defaultOpen = false, onOpenChange }: UseDisclosureOptions = {}) {…