codejump

Interview questionsReact

React interview questions and answers

30 React questions picked from Codejump's quizzes, from the basics a junior is expected to know to the details that trip up senior candidates. Each one comes with its answer and an explanation — try to answer before you open it.

Junior level React questions

  1. 1.Which renders the <Alert /> component only when hasError is true?

    javascript
    // Option A:
    {hasError && <Alert />}
    
    // Option B:
    {hasError ? <Alert /> : null}
    
    // Option C:
    {hasError ? <Alert /> : undefined}
    
    // Option D: All of the above
    • A.Only option A
    • B.Only option B
    • C.Only option A and B
    • D.All three options (A, B, C) render <Alert /> when true and nothing when false
    Show answer

    Answer: D. All three options (A, B, C) render <Alert /> when true and nothing when false

    React renders nothing for null, undefined, and false: All three are semantically equivalent. Option A (&&) is the most concise and common pattern.

    From the quiz React Basics

  2. 2.How do you access a DOM element with useRef?

    javascript
    function FocusInput() {
      const inputRef = useRef(null);
    
      const handleClick = () => {
        inputRef.current.focus(); // (A)
      };
    
      return (
        <>
          <input ref={inputRef} type="text" />
          <button onClick={handleClick}>Focus</button>
        </>
      );
    }
    • A.This is wrong — useRef doesn't work with DOM elements
    • B.inputRef.current is the React element, not the DOM element
    • C.ref={inputRef} attaches the ref to the <input> DOM node; after mount, inputRef.current is the actual DOM element — .focus() calls the native DOM method
    • D.You need document.querySelector("input") instead — refs don't give DOM access
    Show answer

    Answer: C. ref={inputRef} attaches the ref to the <input> DOM node; after mount, inputRef.current is the actual DOM element — .focus() calls the native DOM method

    React assigns ref.current to the DOM node after the component mounts. Before mount (during render), it's still null.

    From the quiz Core Hooks

  3. 3.Which state is unnecessary here?

    javascript
    function CartSummary({ items }) {
      // Is this state or derived?
      const [total, setTotal] = useState(0);
    
      useEffect(() => {
        setTotal(items.reduce((sum, item) => sum + item.price * item.quantity, 0));
      }, [items]);
    
      return <p>Total: ${total}</p>;
    }
    • A.total is derived from items — it should be computed during render, not stored in state; the useEffect adds an extra render cycle unnecessarily
    • B.total should be state — it changes when items change
    • C.This pattern is correct — useEffect is the right place to compute totals
    • D.You should use useReducer for totals
    Show answer

    Answer: A. total is derived from items — it should be computed during render, not stored in state; the useEffect adds an extra render cycle unnecessarily

    Rule: if a value can be computed from props or other state, don't store it in state.

    From the quiz Rendering Basics

  4. 4.When is the default context value used?

    javascript
    const CounterContext = createContext(0); // default: 0
    
    function CounterDisplay() {
      const count = useContext(CounterContext);
      return <p>Count: {count}</p>;
    }
    
    // Scenario A: Wrapped in Provider:
    <CounterContext.Provider value={42}>
      <CounterDisplay /> {/* count = ? */}
    </CounterContext.Provider>
    
    // Scenario B: Not wrapped:
    <CounterDisplay /> {/* count = ? */}
    • A.Scenario A: 0, Scenario B: 42
    • B.Both scenarios return 0
    • C.Scenario A: error, Scenario B: 0
    • D.Scenario A: 42, Scenario B: 0 — the default is used only when no Provider is found in the parent tree
    Show answer

    Answer: D. Scenario A: 42, Scenario B: 0 — the default is used only when no Provider is found in the parent tree

    The default value is useful for: 1. Testing components in isolation (no Provider needed) 2. Library components that work without a Provider

    From the quiz Local State & Lifting

  5. 5.What is the purpose of the key prop?

    javascript
    function List({ items }) {
      return (
        <ul>
          {items.map((item) => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      );
    }
    • A.key helps React identify which items changed, added, or removed — enabling efficient DOM updates
    • B.key is passed as a prop to the <li> component and can be accessed as props.key
    • C.key is required for styling purposes — React won't render without it
    • D.key must be a number — strings are not accepted
    Show answer

    Answer: A. key helps React identify which items changed, added, or removed — enabling efficient DOM updates

    key is a special React prop used for the reconciliation algorithm. When a list re-renders, React uses keys to match old elements to new ones:

    From the quiz React Basics

  6. 6.How do you consume context with useContext?

    javascript
    const ThemeContext = createContext("light");
    
    function ThemedButton() {
      const theme = useContext(ThemeContext);
      return <button className={`btn-${theme}`}>Click</button>;
    }
    
    function App() {
      return (
        <ThemeContext.Provider value="dark">
          <ThemedButton />
        </ThemeContext.Provider>
      );
    }
    • A.ThemedButton receives "light" (the default) — Provider doesn't override it
    • B.useContext requires a second argument specifying which provider to use
    • C.ThemedButton receives "dark" — useContext reads the nearest Provider's value; the default "light" is only used when there's no Provider
    • D.Context only works with class components — useContext returns undefined in functions
    Show answer

    Answer: C. ThemedButton receives "dark" — useContext reads the nearest Provider's value; the default "light" is only used when there's no Provider

    React looks up the component tree to find the nearest ThemeContext.Provider. The default value is the fallback when no provider is found.

    From the quiz Core Hooks

  7. 7.What is the performance impact of images in React?

    javascript
    // ❌ Unoptimized:
    <img src="/hero.png" />  {/* 2MB PNG */}
    
    // ✅ Better (Next.js Image component):
    import Image from "next/image";
    <Image src="/hero.png" width={800} height={600} alt="Hero" priority />
    
    // ✅ Without Next.js:
    <img
      src="/hero-800.webp"
      srcSet="/hero-400.webp 400w, /hero-800.webp 800w"
      sizes="(max-width: 600px) 400px, 800px"
      loading="lazy"
      alt="Hero"
    />
    • A.Images don't affect React performance — they're handled by the browser
    • B.loading="lazy" prevents the image from ever loading
    • C.Images are often the largest assets — unoptimized images hurt LCP (Largest Contentful Paint); optimizations: correct format (WebP), lazy loading, proper sizing, responsive srcSet
    • D.Next.js Image component requires a paid plan
    Show answer

    Answer: C. Images are often the largest assets — unoptimized images hurt LCP (Largest Contentful Paint); optimizations: correct format (WebP), lazy loading, proper sizing, responsive srcSet

    Image optimization impacts:

    From the quiz Rendering Basics

  8. 8.What is the correct way to compose multiple Contexts?

    javascript
    function App() {
      const [theme, setTheme] = useState("light");
      const [user, setUser] = useState(null);
      const [locale, setLocale] = useState("en");
    
      return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
          <UserContext.Provider value={{ user, setUser }}>
            <LocaleContext.Provider value={{ locale, setLocale }}>
              <AppContent />
            </LocaleContext.Provider>
          </UserContext.Provider>
        </ThemeContext.Provider>
      );
    }
    • A.Multiple Contexts are bad — combine them into one
    • B.Multiple focused Contexts are preferred — ThemeContext consumers only re-render on theme changes; UserContext consumers only re-render on user changes; separation of concerns
    • C.Nested Providers cause performance issues — use a flat structure
    • D.You can only have one Context per application
    Show answer

    Answer: B. Multiple focused Contexts are preferred — ThemeContext consumers only re-render on theme changes; UserContext consumers only re-render on user changes; separation of concerns

    Each consumer only subscribes to its relevant context: If you combined everything into one AppContext, EVERY consumer would re-render on ANY change.

    From the quiz Local State & Lifting

  9. 9.What is the correct way to handle a button click?

    javascript
    // Option A:
    <button onClick={handleClick()}>Click</button>
    
    // Option B:
    <button onClick={handleClick}>Click</button>
    
    // Option C:
    <button onClick={() => handleClick()}>Click</button>
    
    // Option D: B and C are both correct
    • A.Only option A — parentheses invoke the function
    • B.Only option B — pass the function reference
    • C.Only option C — arrow function wrapper is required
    • D.B and C are both correct — B passes the function reference, C wraps it in an arrow function
    Show answer

    Answer: D. B and C are both correct — B passes the function reference, C wraps it in an arrow function

    • Option A (onClick={handleClick()}) calls handleClick immediately during render — not on click. The return value of handleClick (usually undefined) is passed as the handler. Almost always a bug.
    • Option B (onClick={handleClick}) passes the function reference — React calls it when clicked.
    • Option C (onClick={() => handleClick()}) wraps in an arrow function — useful when you need to pass arguments: onClick={() => handleClick(item.id)}.

    From the quiz React Basics

  10. 10.Which code violates the Rules of Hooks?

    javascript
    // Option A:
    function Component({ isAdmin }) {
      if (isAdmin) {
        const [role, setRole] = useState("admin"); // ❌?
      }
      return <div />;
    }
    
    // Option B:
    function Component({ isAdmin }) {
      const [role, setRole] = useState(isAdmin ? "admin" : "user"); // ✅?
      return <div />;
    }
    • A.Both options are valid React patterns
    • B.Option A violates the Rules of Hooks — hooks must not be called inside conditionals/loops; Option B is correct — hooks always called, initial value uses the condition
    • C.Option B is invalid — useState can't accept expressions
    • D.Both options are invalid — isAdmin can't be used inside a component
    Show answer

    Answer: B. Option A violates the Rules of Hooks — hooks must not be called inside conditionals/loops; Option B is correct — hooks always called, initial value uses the condition

    Rule: Hooks must be called at the top level — not inside if, for, nested functions. Why? React relies on the order of hook calls to associate state with the right hook:

    From the quiz Core Hooks

Mid level React questions

  1. 11.What is wrong with this pattern?

    javascript
    function UserProfile({ user }) {
      // ❌ Anti-pattern: copying props into state
      const [displayName, setDisplayName] = useState(user.firstName + " " + user.lastName);
    
      return <h2>{displayName}</h2>;
    }
    • A.Nothing — storing derived values in state is correct
    • B.The concatenation should be in useEffect, not useState
    • C.displayName won't update when user prop changes — state only initializes once; this is the "stale state from props" anti-pattern
    • D.useState doesn't accept string expressions
    Show answer

    Answer: C. displayName won't update when user prop changes — state only initializes once; this is the "stale state from props" anti-pattern

    useState only uses the initial value on the first render. If user prop changes, displayName state is not updated:

    From the quiz Component Patterns

  2. 12.What makes a custom hook a "hook"?

    javascript
    function useFetch(url) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(true);
    
      useEffect(() => {
        fetch(url)
          .then(res => res.json())
          .then(data => { setData(data); setLoading(false); });
      }, [url]);
    
      return { data, loading };
    }
    
    // Usage:
    const { data, loading } = useFetch("/api/users");
    • A.Custom hooks must use a class that extends React.Hook
    • B.Custom hooks can only be used once per component
    • C.A custom hook is just a JavaScript function that calls other hooks (starts with use by convention) — it extracts reusable stateful logic while keeping hook rules
    • D.Custom hooks must return JSX
    Show answer

    Answer: C. A custom hook is just a JavaScript function that calls other hooks (starts with use by convention) — it extracts reusable stateful logic while keeping hook rules

    A custom hook is any function that: 1. Starts with use (convention enforced by linters) 2. Calls at least one React hook internally

    From the quiz Custom Hooks

  3. 13.When should you choose Zustand over local useState?

    javascript
    // Scenario: Shopping cart
    // - Cart icon in Header shows item count
    // - Cart page shows all items
    // - Checkout page reads cart total
    // - Product cards have "Add to cart" button
    
    // useState in each component? Or Zustand?
    • A.Zustand is better here: cart is cross-component (Header, Cart, Checkout, ProductCards); all need to read/write the same data; lifting state 4+ levels deep is painful
    • B.Always use Zustand — it's always better than useState
    • C.Use useState in Header and pass via props everywhere
    • D.Create a custom hook with useState inside and export it
    Show answer

    Answer: A. Zustand is better here: cart is cross-component (Header, Cart, Checkout, ProductCards); all need to read/write the same data; lifting state 4+ levels deep is painful

    When Zustand wins:

    From the quiz Context & Reducers

  4. 14.What is "prop drilling" and why is it a problem?

    javascript
    function App() {
      const [user, setUser] = useState({ name: "Alice", role: "admin" });
      return <Dashboard user={user} />;
    }
    
    function Dashboard({ user }) {
      return <Sidebar user={user} />;  // passes user but doesn't use it
    }
    
    function Sidebar({ user }) {
      return <UserBadge user={user} />; // passes user but doesn't use it
    }
    
    function UserBadge({ user }) {
      return <span>{user.name}</span>; // finally uses user
    }
    • A.Dashboard and Sidebar receive user only to pass it down — they don't need it. This "prop drilling" creates unnecessary coupling and makes refactoring harder
    • B.No problem — this is the correct React data flow
    • C.Error — React only allows 2 levels of prop passing
    • D.This causes performance issues because all components re-render
    Show answer

    Answer: A. Dashboard and Sidebar receive user only to pass it down — they don't need it. This "prop drilling" creates unnecessary coupling and makes refactoring harder

    Prop drilling = passing props through intermediate components that don't use them — just to get data to a deeply nested component. Problems:
    • Coupling: Dashboard and Sidebar are coupled to users shape even though they dont use it
    • Refactoring: adding/removing a prop requires touching every intermediate component
    • Readability: harder to see where data flows Solutions: 1. Context API — for global/shared state 2. Component composition — restructure to avoid intermediate passing 3. State management (Zustand, etc.)

    From the quiz Component Patterns

  5. 15.What is the bug in this code?

    javascript
    function Timer({ initialCount }) {
      const [count, setCount] = useState(initialCount);
    
      useEffect(() => {
        const interval = setInterval(() => {
          setCount(count + 1); // ← bug?
        }, 1000);
        return () => clearInterval(interval);
      }, []); // empty deps
    
      return <p>{count}</p>;
    }
    • A.Stale closure bug: count inside the setInterval callback is captured at the initial render (always initialCount) — the count will never increment past initialCount + 1
    • B.No bug — the timer correctly increments every second
    • C.The return cleanup is incorrect — you can't return from an async callback
    • D.setInterval is not allowed in React
    Show answer

    Answer: A. Stale closure bug: count inside the setInterval callback is captured at the initial render (always initialCount) — the count will never increment past initialCount + 1

    Fix with functional update — no closure over count:

    From the quiz Custom Hooks

  6. 16.What is the recommended approach for form state?

    javascript
    // Option A — controlled with useState:
    const [name, setName] = useState("");
    const [email, setEmail] = useState("");
    <input value={name} onChange={e => setName(e.target.value)} />
    
    // Option B — uncontrolled with useRef:
    const nameRef = useRef();
    // <input ref={nameRef} defaultValue="" />
    
    // Option C — react-hook-form:
    const { register, handleSubmit } = useForm();
    // <input {...register("name")} />
    • A.Always use uncontrolled inputs — controlled inputs are slow
    • B.For simple forms: Option A or B work; for complex forms (many fields, validation, submission), react-hook-form (Option C) is preferred — it minimizes re-renders by default
    • C.Never use uncontrolled inputs — they're unidiomatic in React
    • D.Forms must always use Zustand store
    Show answer

    Answer: B. For simple forms: Option A or B work; for complex forms (many fields, validation, submission), react-hook-form (Option C) is preferred — it minimizes re-renders by default

    | Approach | Best for | Performance | |---|---|---| | useState (controlled) | Simple forms, real-time validation | Re-renders on each keystroke | | useRef (uncontrolled) | Simple forms, no real-time feedback | No re-renders | | react-hook-form | Complex forms, schemas, dynamic fields | Minimal re-renders |

    From the quiz Context & Reducers

  7. 17.What happens when you change the key of a component?

    javascript
    function App() {
      const [userId, setUserId] = useState(1);
    
      return (
        <>
          <UserProfile key={userId} userId={userId} />
          <button onClick={() => setUserId(2)}>Switch User</button>
        </>
      );
    }
    
    function UserProfile({ userId }) {
      const [bio, setBio] = useState("");
      // fetches and displays user bio
    }
    • A.Changing key only triggers a re-render without affecting component state
    • B.Changing key causes an error because key is read-only
    • C.key only affects list items — it has no effect on standalone components
    • D.Changing key causes React to unmount the old instance and mount a completely new one — state is reset to initial values
    Show answer

    Answer: D. Changing key causes React to unmount the old instance and mount a completely new one — state is reset to initial values

    When a component's key changes, React treats it as a different component: This is the recommended way to reset component state when a prop changes — much cleaner than tracking changes in useEffect.

    From the quiz Component Patterns

  8. 18.What is the difference between useEffect and useLayoutEffect?

    javascript
    function Tooltip({ position }) {
      const tooltipRef = useRef(null);
      const [adjustedTop, setAdjustedTop] = useState(position.top);
    
      useLayoutEffect(() => {
        const height = tooltipRef.current.getBoundingClientRect().height;
        if (position.top + height > window.innerHeight) {
          setAdjustedTop(position.top - height); // flip tooltip
        }
      }, [position]);
    
      return <div ref={tooltipRef} style={{ top: adjustedTop }}>...</div>;
    }
    • A.useLayoutEffect and useEffect are identical — same timing
    • B.useLayoutEffect is deprecated — always use useEffect instead
    • C.useLayoutEffect fires SYNCHRONOUSLY after DOM mutations but BEFORE the browser paints — ideal for DOM measurements and immediate corrections (no flicker); useEffect fires after paint
    • D.useLayoutEffect runs before the component renders
    Show answer

    Answer: C. useLayoutEffect fires SYNCHRONOUSLY after DOM mutations but BEFORE the browser paints — ideal for DOM measurements and immediate corrections (no flicker); useEffect fires after paint

    With useEffect instead:

    From the quiz Custom Hooks

  9. 19.What is Jotai's atomic state model?

    javascript
    import { atom, useAtom } from "jotai";
    
    const countAtom = atom(0);
    const doubleAtom = atom(get => get(countAtom) * 2); // derived atom
    
    function Counter() {
      const [count, setCount] = useAtom(countAtom);
      return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
    }
    
    function DoubleCount() {
      const [double] = useAtom(doubleAtom);
      return <p>Double: {double}</p>; // automatically updates
    }
    • A.All components sharing countAtom are in the same React tree
    • B.Jotai requires a Provider like Context
    • C.Atoms are independent units of state — derived atoms automatically recompute when their dependencies change; components subscribe to specific atoms, not a global object
    • D.doubleAtom causes two separate API calls
    Show answer

    Answer: C. Atoms are independent units of state — derived atoms automatically recompute when their dependencies change; components subscribe to specific atoms, not a global object

    Jotai's model: vs Zustand: Jotai works bottom-up (atom per state unit) vs Zustand's top-down (one store object).

    From the quiz Context & Reducers

  10. 20.What happens when you try to render an object directly?

    javascript
    const user = { name: "Alice", age: 30 };
    
    function App() {
      return <div>{user}</div>;  // ?
    }
    • A.Renders [object Object]
    • B.React error: "Objects are not valid as a React child"
    • C.Renders { name: "Alice", age: 30 }
    • D.Renders nothing — objects are silently ignored
    Show answer

    Answer: B. React error: "Objects are not valid as a React child"

    React cannot render plain objects as children. It throws: Valid React child types: strings, numbers, booleans, null, undefined, arrays of these, or React elements.

    From the quiz Component Patterns

Senior level React questions

  1. 21.Why does React StrictMode call component functions twice in development?

    javascript
    function App() {
      console.log("App rendered"); // appears twice in console
    
      return <div>Hello</div>;
    }
    
    ReactDOM.createRoot(document.getElementById("root")).render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    );
    • A.A bug in React's development mode
    • B.This behavior also occurs in production
    • C.StrictMode renders components twice for visual consistency
    • D.StrictMode intentionally double-invokes functions to detect side effects — components should be pure functions (same output for same input); double invocation surfaces violations
    Show answer

    Answer: D. StrictMode intentionally double-invokes functions to detect side effects — components should be pure functions (same output for same input); double invocation surfaces violations

    React StrictMode double-invokes (in development only):
    • Function components
    • useState initializer functions
    • useMemo functions
    • useReducer reducers Why: To detect unintentional side effects. Pure functions produce the same result when called twice. If your component logs, fetches, or mutates external state during render — that's a bug StrictMode will expose:

    From the quiz Advanced Patterns

  2. 22.What is the advantage of action payloads in useReducer?

    javascript
    const reducer = (state, action) => {
      switch (action.type) {
        case "FETCH_SUCCESS":
          return { ...state, data: action.payload, loading: false, error: null };
        case "FETCH_ERROR":
          return { ...state, error: action.payload, loading: false };
        case "RETRY":
          return { ...state, loading: true, error: null, retries: state.retries + 1 };
        default:
          return state;
      }
    };
    
    dispatch({ type: "FETCH_SUCCESS", payload: data });
    dispatch({ type: "FETCH_ERROR", payload: new Error("Network failed") });
    • A.Action payloads are optional and rarely used
    • B.Payloads carry data for the transition — the reducer produces a new state from (currentState, action) deterministically; this makes transitions testable and predictable without component context
    • C.You can only have one active action type at a time
    • D.useReducer actions must be pure strings
    Show answer

    Answer: B. Payloads carry data for the transition — the reducer produces a new state from (currentState, action) deterministically; this makes transitions testable and predictable without component context

    The reducer pattern:

    From the quiz Advanced Hook Patterns

  3. 23.What does Immer enable in state management?

    javascript
    import { produce } from "immer";
    
    const reducer = (state, action) => {
      return produce(state, draft => {
        switch (action.type) {
          case "ADD_COMMENT":
            // Nested update — looks like mutation, produces new state:
            draft.posts[action.postId].comments.push(action.comment);
            break;
          case "MARK_READ":
            draft.notifications.forEach(n => { n.read = true; });
            break;
        }
      });
    };
    • A.Immer's produce creates a structural clone ("draft") — any mutations on the draft produce a new immutable state; the original state is never modified
    • B.produce actually mutates the original state
    • C.Immer is slower than spreading nested objects manually
    • D.Immer requires all state to be serializable JSON
    Show answer

    Answer: A. Immer's produce creates a structural clone ("draft") — any mutations on the draft produce a new immutable state; the original state is never modified

    Without Immer — deep nested updates are verbose: Immer uses JavaScript Proxies to track mutations and creates a minimal structural copy.

    From the quiz Global State

  4. 24.What is a React Portal used for?

    javascript
    import { createPortal } from "react-dom";
    
    function Modal({ isOpen, children }) {
      if (!isOpen) return null;
      return createPortal(
        <div className="modal-overlay">{children}</div>,
        document.getElementById("modal-root")
      );
    }
    • A.Portals allow rendering in a Web Worker outside the main thread
    • B.Portals render children into a different DOM node than the component's parent — useful for modals, tooltips, dropdowns that need to escape CSS overflow/z-index constraints
    • C.Portals create a separate React tree that doesn't receive context from the parent
    • D.Portals are required for server-side rendering
    Show answer

    Answer: B. Portals render children into a different DOM node than the component's parent — useful for modals, tooltips, dropdowns that need to escape CSS overflow/z-index constraints

    Portals render children into a different DOM location, but the React tree hierarchy is preserved: The modal escapes overflow: hidden constraints of its ancestors while staying in React's tree for events, context, and state.

    From the quiz Advanced Patterns

  5. 25.What causes the infinite loop?

    javascript
    function Component() {
      const [items, setItems] = useState([]);
    
      useEffect(() => {
        const processed = items.map(item => ({ ...item, processed: true }));
        setItems(processed); // ← triggers re-render → effect runs again → infinite!
      }, [items]); // items is a dependency
    }
    • A.setItems(processed) creates a new array reference → items dependency changes → effect runs again → infinite loop; the state update triggers the effect that triggered the state update
    • B.This is fine — React detects circular updates and stops automatically
    • C.The issue is using .map() inside useEffect
    • D.Setting state in useEffect is always prohibited
    Show answer

    Answer: A. setItems(processed) creates a new array reference → items dependency changes → effect runs again → infinite loop; the state update triggers the effect that triggered the state update

    Fixes:

    From the quiz Advanced Hook Patterns

  6. 26.How should you categorize state?

    javascript
    // Server state examples:
    const { data: users } = useQuery({ queryKey: ["users"], queryFn: fetchUsers });
    const { data: profile } = useQuery({ queryKey: ["profile"], queryFn: fetchProfile });
    
    // Client state examples:
    const [isModalOpen, setIsModalOpen] = useState(false);
    const [selectedTab, setSelectedTab] = useState("overview");
    • A.Server state (lives on server, fetched async, can be stale): use React Query/SWR/RTK Query; Client state (lives in browser, user-controlled): use useState/Zustand; they have different characteristics and needs
    • B.All state should be managed the same way
    • C.Server state should be stored in Zustand after fetching
    • D.You should never cache server state in the client
    Show answer

    Answer: A. Server state (lives on server, fetched async, can be stale): use React Query/SWR/RTK Query; Client state (lives in browser, user-controlled): use useState/Zustand; they have different characteristics and needs

    | | Server State | Client State | |---|---|---| | Lives in | Database/API | Browser memory | | Asynchronous | ✅ Always | ❌ Usually sync | | Can be stale | ✅ Yes | ❌ Always fresh | | Shared by | Many users | One user/session | | Tools | React Query, RTK Query | useState, Zustand |

    From the quiz Global State

  7. 27.Why is cleanup important in useEffect?

    javascript
    useEffect(() => {
      const subscription = eventBus.subscribe("update", handler);
    
      return () => {
        subscription.unsubscribe(); // cleanup
      };
    }, []);
    • A.Cleanup is optional — React garbage-collects all subscriptions automatically
    • B.Cleanup only runs when the effect dependencies change, not on unmount
    • C.Without cleanup, subscriptions/intervals persist after the component unmounts — causing memory leaks and calling state updates on unmounted components
    • D.Cleanup runs before each render, not just on unmount
    Show answer

    Answer: C. Without cleanup, subscriptions/intervals persist after the component unmounts — causing memory leaks and calling state updates on unmounted components

    Without cleanup: With cleanup: The cleanup function runs: 1. Before the effect runs again (when deps change) 2. When the component unmounts

    From the quiz Advanced Patterns

  8. 28.Why use useRef for a callback that needs fresh state?

    javascript
    function useEventCallback(callback) {
      const callbackRef = useRef(callback);
    
      useLayoutEffect(() => {
        callbackRef.current = callback;
      });
    
      return useCallback(() => {
        callbackRef.current();
      }, []); // stable reference!
    }
    • A.This pattern is unnecessary — useCallback with deps solves the same problem
    • B.useLayoutEffect is wrong here — should use useEffect
    • C.This creates a stable function reference (empty useCallback deps) while the callback inside always reads the latest closure — solving the "stale callback but need fresh state" tradeoff
    • D.This pattern violates Rules of Hooks
    Show answer

    Answer: C. This creates a stable function reference (empty useCallback deps) while the callback inside always reads the latest closure — solving the "stale callback but need fresh state" tradeoff

    The dilemma: This pattern is used in React documentation and libraries like react-use.

    From the quiz Advanced Hook Patterns

  9. 29.What is the full optimistic update pattern with React Query?

    javascript
    const mutation = useMutation({
      mutationFn: (newTodo) => api.createTodo(newTodo),
      onMutate: async (newTodo) => {
        await queryClient.cancelQueries({ queryKey: ["todos"] });
        const previousTodos = queryClient.getQueryData(["todos"]);
        queryClient.setQueryData(["todos"], old => [...old, { ...newTodo, id: "temp" }]);
        return { previousTodos }; // context for rollback
      },
      onError: (err, newTodo, context) => {
        queryClient.setQueryData(["todos"], context.previousTodos); // rollback!
      },
      onSettled: () => {
        queryClient.invalidateQueries({ queryKey: ["todos"] }); // refetch
      },
    });
    • A.Rollback is not possible with React Query
    • B.onMutate saves the previous state and applies the optimistic update; onError rolls back to the saved state; onSettled ensures the server is the source of truth with a refetch
    • C.cancelQueries cancels the mutation itself
    • D.Optimistic updates require Redux Toolkit to work
    Show answer

    Answer: B. onMutate saves the previous state and applies the optimistic update; onError rolls back to the saved state; onSettled ensures the server is the source of truth with a refetch

    The lifecycle:

    From the quiz Global State

  10. 30.What is the performance issue with this Context pattern?

    javascript
    const ThemeContext = createContext(null);
    
    function App() {
      const [theme, setTheme] = useState("light");
      const [user, setUser] = useState({ name: "Alice" });
    
      return (
        <ThemeContext.Provider value={{ theme, setTheme, user, setUser }}>
          <DeepTree />
        </ThemeContext.Provider>
      );
    }
    • A.No issue — Context is always efficient
    • B.Every time user changes, ALL context consumers re-render — even those that only use theme. The entire value object is recreated each render
    • C.Context only works one level deep
    • D.setTheme and setUser cause circular updates
    Show answer

    Answer: B. Every time user changes, ALL context consumers re-render — even those that only use theme. The entire value object is recreated each render

    Context uses reference equality for change detection. A new object { theme, setTheme, user, setUser } is created on every render: Solutions:

    From the quiz Advanced Patterns

Practise 500+ more React questions

Timed quizzes with instant feedback, the questions you miss coming back until they stick, coding challenges and AI mock interviews — free to start.