Back to interview questions

100+ React Interview Questions and Answers

100+ React Interview Questions and Answers

Explore 120 React interview questions and answers on components, Hooks, state, effects, rendering, performance, testing, accessibility, and architecture.

Explore 120 React interview questions and answers on components, Hooks, state, effects, rendering, performance, testing, accessibility, and architecture.

By

Team Sherlock.sh

Add Sherlock as a preferred source on Google

React interviews test more than familiarity with JSX and a list of Hooks. Strong candidates can explain why an interface renders, choose a reliable state model, synchronize with external systems, and investigate a slow or incorrect interaction. Recruiters should look for developers who can connect component code to user experience, accessibility, tests, and production behavior.

This guide to 100+ React Interview Questions and Answers contains 120 questions across beginner, intermediate, and experienced levels. It covers components, state, Hooks, effects, forms, concurrent rendering, performance, testing, accessibility, server rendering, and architectural decisions. The examples target React for the web and distinguish the React library from framework-specific routing, data-loading, and deployment features.

How can recruiters detect AI-assisted cheating, deepfakes, and impersonation during React interviews?

A React interview might ask a candidate to explain a stale closure, repair an Effect cleanup, or investigate why a form loses its state. A fluent explanation can hide a skills gap when the candidate relies on unauthorized real-time assistance instead of reasoning independently about components, JavaScript, and browser behavior.

Sherlock AI helps recruiters detect potential AI-assisted cheating, deepfakes, candidate impersonation, and unauthorized real-time assistance during React interviews.

Pair interview-integrity checks with a small code or debugging scenario. Change a dependency, introduce a delayed response, or ask how the candidate would test the proposed fix. Review suspicious signals in context rather than treating any single signal as proof of misconduct.

React Interview Questions

Select questions that reflect the role and application. A UI developer, full-stack engineer, and frontend architect need different depth. State the React version, rendering environment, framework, data requirements, browser support, and accessibility needs before evaluating a solution.

React Interview Questions for Beginners

Start with components, JSX, props, state, events, lists, forms, Hooks, and basic testing. These questions establish whether a candidate can explain how a small interactive interface works and avoid common state and rendering mistakes.

  1. What is React, and what problems does it solve?

  2. How is React different from a framework and from React Native?

  3. What is a React component?

  4. What is JSX, and how does it differ from HTML?

  5. How do you embed JavaScript expressions in JSX?

  6. What are props, and why should a component treat them as read-only?

  7. How does the children prop support composition?

  8. What is state, and how is it different from a local variable?

  9. How do you declare and update state with useState?

  10. Why does reading state immediately after setting it show the previous value?

  11. When should you use a functional state updater?

  12. How do you update an object in state without mutating it?

  13. How do you add, remove, or update items in an array stored in state?

  14. How do you handle user events in React?

  15. What is the difference between passing an event handler and calling it during render?

  16. How do you render content conditionally?

  17. How do you render a list, and why does each item need a key?

  18. Why can array indexes and random values be poor list keys?

  19. What are Fragments, and when do you use an explicit Fragment?

  20. What happens during React rendering and committing?

  21. What does it mean for a component to be pure?

  22. How does data flow between parent and child components?

  23. When should you lift state up?

  24. What are controlled and uncontrolled inputs?

  25. How do you handle text, checkbox, and select inputs?

  26. How do you submit a form without accidentally reloading the page?

  27. How do you style React components?

  28. What are Hooks, and what are the Rules of Hooks?

  29. What is useEffect intended for?

  30. How does an Effect dependency array affect synchronization?

  31. Why does an Effect sometimes need a cleanup function?

  32. How is useRef different from useState?

  33. How can a component focus a DOM input?

  34. What is Context, and when is it useful?

  35. What does StrictMode check during development?

  36. How do you display loading, empty, error, and success states?

  37. How can you make a basic React form accessible?

  38. How does React render text safely, and what changes with dangerouslySetInnerHTML?

  39. What are React Developer Tools used for?

  40. How would you test a simple interactive React component?

React Interview Questions for Intermediate

Explore reusable component design, complex state, effect lifecycles, memoization, transitions, asynchronous UI, forms, hydration, and integration. Ask candidates to explain both the implementation and the cases that can break it.

  1. How do you choose a state structure that avoids contradictory and duplicated values?

  2. When should you calculate a value during rendering instead of storing it in state?

  3. How do component position, type, and key control state preservation?

  4. How does useReducer organize complex state transitions?

  5. How can Context and a reducer work together without becoming a global-state dumping ground?

  6. What does a custom Hook share, and what does it not share?

  7. How do you diagnose stale closures in event handlers and Effects?

  8. How do you fix an Effect that keeps rerunning in a loop?

  9. How do you safely fetch data in an Effect without showing an outdated response?

  10. Why might a framework or query library be better than fetching every resource in Effects?

  11. How do useEffect and useLayoutEffect differ?

  12. What is useEffectEvent, and when should it be used?

  13. When does useMemo help, and why is it not a correctness guarantee?

  14. When does useCallback help preserve a useful function identity?

  15. What does memo skip, and what can still make a memoized component render?

  16. How does React Compiler change manual memoization decisions?

  17. How does React batch state updates?

  18. What does useTransition do for an interaction?

  19. How is useDeferredValue different from debouncing?

  20. How do lazy and Suspense support code splitting?

  21. What kinds of asynchronous work can a Suspense boundary handle?

  22. What is an error boundary, and what errors does it not catch?

  23. How do portals affect DOM placement, Context, and event propagation?

  24. How do you expose a focused imperative API with refs and useImperativeHandle?

  25. How does useId help connect accessible controls and descriptions?

  26. How do you type component props, events, and refs with TypeScript?

  27. How do you design reusable components without creating excessive prop complexity?

  28. How do form Actions and useActionState manage submission state?

  29. How does useFormStatus read the status of a parent form?

  30. How does useOptimistic model a temporary UI update?

  31. What is the use API, and how does it differ from ordinary Hooks?

  32. How does useSyncExternalStore integrate an external store safely?

  33. What causes hydration mismatches, and how do you fix them?

  34. How do server-side rendering and client rendering differ from Server Components?

  35. What do use client and use server mark in an RSC-capable application?

  36. What is Activity used for, and what happens to hidden Effects?

  37. How do ViewTransition and Fragment refs fit into React 19.3?

  38. How do you test asynchronous UI updates without relying on arbitrary delays?

  39. How do you diagnose a slow list before adding memoization or virtualization?

  40. How would you implement a resilient search interface with keyboard access?

React Interview Questions for Experienced

Use these scenarios to assess architectural judgment and ownership of production outcomes. Strong responses connect user requirements to state boundaries, rendering strategy, security, test coverage, deployment, and observability.

  1. How would you choose React application boundaries and a rendering architecture?

  2. How would you choose between local state, Context, an external store, and a server-state cache?

  3. How would you migrate a large class-component application incrementally?

  4. How would you plan a React version upgrade without breaking production workflows?

  5. How would you introduce React Compiler into a mature codebase?

  6. How would you diagnose an interaction that is slow even though React renders quickly?

  7. How would you interpret Profiler measurements without optimizing the wrong bottleneck?

  8. How would you design Suspense boundaries to avoid unnecessary loading waterfalls?

  9. How would you design streaming SSR with meaningful loading and error recovery?

  10. How would you investigate an intermittent hydration failure in production?

  11. How would you design Server and Client Component boundaries to reduce client JavaScript?

  12. How would you secure Server Functions that accept untrusted input?

  13. How would you prevent one user's data from leaking through server caches?

  14. How would you decide whether a value belongs in the URL, client state, or persistent storage?

  15. How would you handle optimistic mutations when updates fail or arrive out of order?

  16. How would you prevent duplicate submissions and stale writes in a React application?

  17. How would you build a real-time dashboard without overwhelming rendering?

  18. How would you manage external subscriptions under concurrent rendering and StrictMode?

  19. How would you design a virtualized, accessible data grid?

  20. How would you improve a large form without making validation or accessibility fragile?

  21. How would you build a reusable accessible dialog with portals and focus management?

  22. How would you choose error-boundary placement and production error telemetry?

  23. How would you create a component testing strategy across unit, integration, and end-to-end tests?

  24. How would you test a Suspense, transition, or optimistic-update workflow?

  25. How would you design a typed component API for a shared design system?

  26. How would you introduce a third-party imperative widget safely into React?

  27. How would you support offline edits and reconnect reconciliation?

  28. How would you design multi-tenant theming and localization without unnecessary rerenders?

  29. How would you investigate a memory leak in a long-lived React page?

  30. How would you use transitions without hiding correctness or race-condition problems?

  31. How would you adopt Activity while protecting background-resource usage?

  32. How would you add View Transitions while respecting accessibility and browser support?

  33. How would you reduce JavaScript bundle cost and verify the result?

  34. How would you prevent XSS and unsafe URL handling in a React frontend?

  35. How would you use feature flags and staged rollouts without creating inconsistent UI state?

  36. How would you evaluate micro-frontends for a React organization?

  37. How would you share code between React web and React Native without conflating platforms?

  38. How would you review a custom Hook API for correctness and maintainability?

  39. How would you diagnose a production bug that disappears when logging is added?

  40. How would you lead a technical review of an unfamiliar React application?

React Interview Questions and Answers

These answers follow the question bank in the same order and begin with direct responses candidates can adapt in an interview. Code examples illustrate the named behavior; surrounding application code, error handling, and build configuration are included only when relevant. Verify the actual React, framework, compiler, and testing-tool versions before adapting them.

React Interview Questions and Answers for Beginners

The beginner answers connect core React concepts to practical UI behavior. They emphasize immutable state updates, stable identity, accessible controls, and the distinction between rendering and synchronization.

1. What is React, and what problems does it solve?

React is a JavaScript library for building user interfaces from components. A component describes what should appear for a particular set of inputs, rather than issuing a separate DOM command for every change. When application state changes, React calculates the next interface and updates the relevant host output, such as browser DOM elements.

This model is useful when a screen has many related interactive states. In a shopping cart, the item list, quantity controls, and total can all be derived from the same data instead of being manually kept in sync. Components also create reusable boundaries for UI behavior and presentation.

React does not automatically solve every application problem. Routing, persistence, authorization, data fetching policies, and deployment still need deliberate choices. Its central contribution is a composable, declarative way to manage changing interfaces, not a guarantee that any React application will be fast or well structured.

2. How is React different from a framework and from React Native?

React supplies the component and rendering model, but it does not prescribe a complete application architecture. A framework built around React typically integrates additional concerns such as routing, loading data, bundling, and server rendering. Using a React framework still means using React; the framework supplies conventions and infrastructure around it. Modern React guidance recommends a framework for many new applications, although building from scratch remains possible.

React Native uses React's programming model to build native applications. Its core components include View and Text, whereas a React web interface normally uses React DOM and elements such as div and p. React Native is not simply a website displayed inside a mobile WebView.

State logic and some domain code can be shared between web and native projects. DOM APIs, navigation integrations, styling details, and platform-specific UI generally require separate treatment.

3. What is a React component?

A React component is a reusable unit of UI with its own rendering logic. In modern React, it is usually a JavaScript function that receives props and returns renderable content. Components can contain other components, so a page can be assembled from smaller pieces with clear responsibilities.

JSX example

export default function Availability({ inStock }) {
  return <p>{inStock ? 'Available now' : 'Out of stock'}</p>;
}
export default function Availability({ inStock }) {
  return <p>{inStock ? 'Available now' : 'Out of stock'}</p>;
}
export default function Availability({ inStock }) {
  return <p>{inStock ? 'Available now' : 'Out of stock'}</p>;
}

A parent uses this component as <Availability inStock={true} />. The capitalized name distinguishes a custom component from a built-in element such as <p>. React should call component functions through JSX; application code should not invoke them like ordinary helper functions.

A component is not the same as a DOM element. It can produce several elements, another component, text, or no visible output. Keep component definitions outside other component functions so their identities do not change on every parent render.

4. What is JSX, and how does it differ from HTML?

JSX is a JavaScript syntax extension for describing element trees. It looks similar to HTML, but it is transformed by a build tool into JavaScript that creates React elements. JSX is not an HTML string, and browsers do not execute untransformed JSX as ordinary JavaScript.

Several differences matter in practice: tags must be closed, most DOM property names use camelCase, CSS classes use className, and labels use htmlFor. JavaScript expressions can appear inside braces. ARIA and data-* attributes keep their hyphenated spelling.

JSX example

export default function EmailField() {
  return (
    <label className="field">
      Email
      <input type="email" name="email" />
    </label>
  );
}
export default function EmailField() {
  return (
    <label className="field">
      Email
      <input type="email" name="email" />
    </label>
  );
}
export default function EmailField() {
  return (
    <label className="field">
      Email
      <input type="email" name="email" />
    </label>
  );
}

Adjacent JSX elements in one expression need a common wrapper, which may be a Fragment rather than an extra DOM node. JSX is convenient but optional: React also provides APIs for constructing elements without it.

5. How do you embed JavaScript expressions in JSX?

Use curly braces to insert a JavaScript expression into JSX content or a prop value. The expression can read a variable, perform arithmetic, call a pure formatting function, or choose between values with a conditional expression. Quoted attributes are literal strings, so title="name" and title={name} mean different things.

JSX example

export default function OrderSummary({ quantity, unitPrice }) {
  const total = quantity * unitPrice;
  return (
    <p title={`Quantity: ${quantity}`}>
      Total: ${total.toFixed(2)}
    </p>
  );
}
export default function OrderSummary({ quantity, unitPrice }) {
  const total = quantity * unitPrice;
  return (
    <p title={`Quantity: ${quantity}`}>
      Total: ${total.toFixed(2)}
    </p>
  );
}
export default function OrderSummary({ quantity, unitPrice }) {
  const total = quantity * unitPrice;
  return (
    <p title={`Quantity: ${quantity}`}>
      Total: ${total.toFixed(2)}
    </p>
  );
}

Braces accept expressions, not statements such as if or for; perform more involved control flow before returning JSX. Plain objects are not valid visible children, so render a specific property rather than {customer}. Double braces in style={{ color: 'navy' }} are simply an object literal inside the JSX expression braces, not a separate React syntax.

6. What are props, and why should a component treat them as read-only?

Props are values a parent supplies to a component. They can include strings, numbers, objects, functions, and JSX. A child reads them to decide what to render or how to respond to an interaction. Props are a snapshot of the parent's chosen inputs for that render, not storage owned by the child.

A child must not change a received object or array in place. For example, pushing into a products prop can also change data observed by the parent or another child, making behavior depend on rendering order. Instead, the child can call a callback such as onProductAdded, and the owner updates its state to provide new props.

Read-only does not mean props never change. A later render can receive different values. Destructuring with a default, such as function Badge({ tone = 'neutral' }), supplies that default for an omitted or undefined prop, but not for null.

7. How does the children prop support composition?

The children prop contains the content nested between a component's opening and closing tags. It lets a component own a surrounding structure without knowing every possible thing that might appear inside it. That is composition: combining focused components rather than making one component handle every variation through flags.

JSX example

function Panel({ title, children }) {
  return (
    <section>
      <h2>{title}</h2>
      {children}
    </section>
  );
}

export default function Profile() {
  return <Panel title="Profile"><p>Your profile is ready.</p></Panel>;
}
function Panel({ title, children }) {
  return (
    <section>
      <h2>{title}</h2>
      {children}
    </section>
  );
}

export default function Profile() {
  return <Panel title="Profile"><p>Your profile is ready.</p></Panel>;
}
function Panel({ title, children }) {
  return (
    <section>
      <h2>{title}</h2>
      {children}
    </section>
  );
}

export default function Profile() {
  return <Panel title="Profile"><p>Your profile is ready.</p></Panel>;
}

Here, Panel handles the section and heading, while its caller supplies the body. Children may be text, elements, arrays, or empty values; do not assume they are always an array. Prefer rendering them directly when no transformation is needed. For multiple named regions, explicit props such as header and footer may communicate the contract more clearly.

8. What is state, and how is it different from a local variable?

State is information React remembers for a component between renders, such as the selected tab or the text being edited. Updating state through its setter requests another render so the interface can reflect the new value.

A local variable inside the component function belongs to that particular function call. A statement such as let selected = 0 runs again when the component renders, and assigning to that variable does not tell React to update the screen. It is therefore unsuitable for remembering an interactive selection across renders.

State is associated with a component's identity and position in the rendered tree, not merely with its function name. Two rendered instances can have independent state. Local variables remain useful for intermediate calculations such as a filtered list derived from props and state. A value does not need its own state just because it appears in the UI.

9. How do you declare and update state with useState?

Import useState and call it at the top level of a function component or custom Hook. It returns the current value and a setter. Pass the initial value on the first render, then call the setter from an appropriate interaction to request a new value.

JSX example

import { useState } from 'react';

export default function Quantity() {
  const [quantity, setQuantity] = useState(1);
  return (
    <button onClick={() => setQuantity(q => q + 1)}>
      Quantity: {quantity}
    </button>
  );
}
import { useState } from 'react';

export default function Quantity() {
  const [quantity, setQuantity] = useState(1);
  return (
    <button onClick={() => setQuantity(q => q + 1)}>
      Quantity: {quantity}
    </button>
  );
}
import { useState } from 'react';

export default function Quantity() {
  const [quantity, setQuantity] = useState(1);
  return (
    <button onClick={() => setQuantity(q => q + 1)}>
      Quantity: {quantity}
    </button>
  );
}

The setter replaces this state value; it does not automatically merge object fields. For expensive initialization, pass a pure initializer function, such as useState(() => buildInitialData()), instead of repeatedly calculating an argument during rendering. React may call initializers twice during development checks, so they must not perform external side effects. Do not call useState conditionally or inside an event handler.

10. Why does reading state immediately after setting it show the previous value?

Each render receives a snapshot of state. Calling a setter queues an update for a later render; it does not change the variable captured by the currently executing event handler. This is more precise than saying that state is simply asynchronous.

For example, in a handler created when count is 4, setCount(count + 1); console.log(count); logs 4. The next render can receive 5, but the existing handler still belongs to the earlier snapshot. A timer created by that handler also captures the values from that render unless the code deliberately uses another mechanism.

If the handler needs the calculated next value immediately, calculate it once: const next = count + 1, then use next and pass it to the setter. If an update depends on earlier queued updates, use a functional updater instead. Awaiting a state setter is not a way to await a completed render.

11. When should you use a functional state updater?

Use a functional state updater when the next value is calculated from the previous value of that same state. React queues the updater and supplies the pending value when processing it, allowing several updates to compose instead of all reading one old snapshot.

For example, two consecutive calls to setCount(c => c + 1) increase the count by two. Two calls to setCount(count + 1) in the same handler usually request the same replacement value, because both use that render's count.

An updater must be pure: calculate and return the next state without sending a request, modifying another object, or triggering another unrelated action. Development checks can invoke it more than once. Use a direct value when the next value is already known, such as setSearchText(event.target.value). An updater solves dependencies on the state being updated; it does not automatically refresh every other prop or variable captured by its closure.

12. How do you update an object in state without mutating it?

Create a new object that preserves unchanged fields and replaces the changed fields, then pass it to the setter. Treat the existing state object as a snapshot that other code may still be using. With nested data, copy every object along the path you change; object spread only makes a shallow copy.

For a component whose state includes a nested address, the event-handler update can be:

JSX example

// Inside a component with a setCustomer state setter:
function changeCity(city) {
  setCustomer(previous => ({
    ...previous,
    address: { ...previous.address, city }
  }));
}
// Inside a component with a setCustomer state setter:
function changeCity(city) {
  setCustomer(previous => ({
    ...previous,
    address: { ...previous.address, city }
  }));
}
// Inside a component with a setCustomer state setter:
function changeCity(city) {
  setCustomer(previous => ({
    ...previous,
    address: { ...previous.address, city }
  }));
}

This creates a new customer object and a new address while preserving other fields. Copying only the outer object and then assigning to copy.address.city would still mutate the old address. Deep-cloning the entire state is usually unnecessary; unchanged branches can remain shared. For deeply nested updates, consider a simpler state shape or an immutable-update helper.

13. How do you add, remove, or update items in an array stored in state?

Produce a new array rather than changing the array already in state. Spread or concat can add an item, filter can remove one, and map can replace a matching item. When an item is an object, copy that object as well if one of its fields changes.

These are event-handler excerpts for an items state array:

JSX example

// nextItem and targetId come from the current interaction.
setItems(previous => [...previous, nextItem]);
setItems(previous => previous.filter(item => item.id !== targetId));
setItems(previous => previous.map(item =>
  item.id === targetId ? { ...item, done: true } : item
));
// nextItem and targetId come from the current interaction.
setItems(previous => [...previous, nextItem]);
setItems(previous => previous.filter(item => item.id !== targetId));
setItems(previous => previous.map(item =>
  item.id === targetId ? { ...item, done: true } : item
));
// nextItem and targetId come from the current interaction.
setItems(previous => [...previous, nextItem]);
setItems(previous => previous.filter(item => item.id !== targetId));
setItems(previous => previous.map(item =>
  item.id === targetId ? { ...item, done: true } : item
));

They illustrate separate operations, not a sequence required for one action. Avoid mutating methods such as push or splice on state. Before using sort or reverse, copy the array. A new array alone is not sufficient if its existing objects are then mutated. Stable item identifiers make both updates and rendering more reliable.

14. How do you handle user events in React?

Pass a function to the relevant event prop, such as onClick, onChange, or onSubmit. React calls it when the interaction occurs. The handler can read the event, update state, and perform work that belongs to that specific user action.

JSX example

export default function RemoveButton({ itemId, onRemove }) {
  return (
    <button type="button" onClick={() => onRemove(itemId)}>
      Remove item
    </button>
  );
}
export default function RemoveButton({ itemId, onRemove }) {
  return (
    <button type="button" onClick={() => onRemove(itemId)}>
      Remove item
    </button>
  );
}
export default function RemoveButton({ itemId, onRemove }) {
  return (
    <button type="button" onClick={() => onRemove(itemId)}>
      Remove item
    </button>
  );
}

The component delegates the actual removal through a callback, so the parent can own the data. For browser events, event.currentTarget refers to the element with the handler, while event.target may be a nested element where the event began. preventDefault() cancels a browser default action; stopPropagation() stops propagation through the event path. They solve different problems. Prefer a real button for button behavior so keyboard activation and semantics are available without recreating them manually.

15. What is the difference between passing an event handler and calling it during render?

Passing onClick={handleSave} gives React a function to call later. Writing onClick={handleSave()} calls handleSave while the component is rendering and assigns its return value as the handler. If it returns nothing, there is no callable handler left for the click; if it updates state, it may trigger a render loop.

When arguments are required, pass a wrapper function: onClick={() => handleSave(documentId)}. The wrapper is created during rendering, but its body does not run until the event occurs. This distinction is between creating or passing a function and executing it, not between named functions and arrow functions.

A function call can be appropriate when it intentionally returns another function, but that is a different API contract and should be clear. Rendering must not cause a save request merely because React needs to calculate the UI again; the save belongs to the user's interaction.

16. How do you render content conditionally?

Use ordinary JavaScript control flow to decide which JSX to return. An early if return works well for substantially different screens, a ternary selects between two values, and && can include an element only when a condition is true. Returning null produces no visible content for that component.

JSX example

export default function StockMessage({ quantity }) {
  if (quantity === 0) return <p>Out of stock</p>;
  return <p>{quantity > 5 ? 'In stock' : 'Only a few left'}</p>;
}
export default function StockMessage({ quantity }) {
  if (quantity === 0) return <p>Out of stock</p>;
  return <p>{quantity > 5 ? 'In stock' : 'Only a few left'}</p>;
}
export default function StockMessage({ quantity }) {
  if (quantity === 0) return <p>Out of stock</p>;
  return <p>{quantity > 5 ? 'In stock' : 'Only a few left'}</p>;
}

Be careful with quantity && <p>Available</p>: when quantity is zero, the expression evaluates to the number 0, which React can display. Use an explicit Boolean condition such as quantity > 0 instead. Keep conditional branches readable rather than stacking nested ternaries. Hooks must still be called in the same order, so do not put a useState or useEffect call inside one of these branches.

17. How do you render a list, and why does each item need a key?

Transform an array into JSX with map, assigning a stable key to the outermost element returned for each item. The key tells React which sibling represents the same logical item across renders, even when items are inserted, removed, or reordered.

JSX example

export default function ProjectList({ projects }) {
  return (
    <ul>
      {projects.map(project => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  );
}
export default function ProjectList({ projects }) {
  return (
    <ul>
      {projects.map(project => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  );
}
export default function ProjectList({ projects }) {
  return (
    <ul>
      {projects.map(project => (
        <li key={project.id}>{project.name}</li>
      ))}
    </ul>
  );
}

Keys must be unique among siblings, but they do not need to be globally unique. Prefer an identifier that already belongs to the data. If a list renders a custom component, put the key on that component at the mapping site, not on an element hidden inside its implementation. React uses key internally and does not pass it as a normal prop, so pass projectId={project.id} separately if the child needs that value.

18. Why can array indexes and random values be poor list keys?

An array index describes a position, not an item's identity. If a row is inserted at the beginning, every later index now refers to a different item. React may preserve a row component's state at that position, causing an edit draft or selection to appear attached to the wrong record.

A random key generated during rendering has the opposite problem: the same item receives a different identity on every render. React recreates its subtree, potentially losing local state and input focus while causing unnecessary work.

Use a persistent data identifier. For locally created items, generate an ID when creating the item and retain it with the data, rather than generating it while mapping the list. Index keys can be acceptable for truly fixed lists whose order and membership never change. Changing a key intentionally can reset a component, but that behavior should be deliberate rather than an accidental consequence of key selection.

19. What are Fragments, and when do you use an explicit Fragment?

A Fragment groups several children without inserting a wrapper element into the DOM. It is useful when a component needs to return adjacent elements while preserving the surrounding layout or valid HTML structure. The short form is <>...</>.

Use the explicit Fragment component when the group needs a key, such as a list where each record produces both a term and a description:

JSX example

import { Fragment } from 'react';

export default function Glossary({ entries }) {
  return (
    <dl>
      {entries.map(entry => (
        <Fragment key={entry.id}>
          <dt>{entry.term}</dt>
          <dd>{entry.definition}</dd>
        </Fragment>
      ))}
    </dl>
  );
}
import { Fragment } from 'react';

export default function Glossary({ entries }) {
  return (
    <dl>
      {entries.map(entry => (
        <Fragment key={entry.id}>
          <dt>{entry.term}</dt>
          <dd>{entry.definition}</dd>
        </Fragment>
      ))}
    </dl>
  );
}
import { Fragment } from 'react';

export default function Glossary({ entries }) {
  return (
    <dl>
      {entries.map(entry => (
        <Fragment key={entry.id}>
          <dt>{entry.term}</dt>
          <dd>{entry.definition}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

In React 19.3, Fragment refs are also stable and require the explicit syntax. They expose a FragmentInstance for supported interactions with the grouped DOM nodes; they do not create an invisible DOM element. A Fragment itself is not a styling wrapper.

20. What happens during React rendering and committing?

Rendering is the calculation phase: React calls components to determine the next UI from their current inputs. Committing is the phase in which React applies the necessary host changes, such as creating, updating, or removing browser DOM nodes. The browser then performs its own layout and painting work.

An update can cause a component to render without changing any DOM. If the calculated output is unchanged, there may be nothing to modify. Rendering therefore does not mean replacing the entire page, and a render count alone is not a measure of visible updates or performance cost.

Render logic must stay pure because React can repeat or discard render work before committing it. Do not start subscriptions or modify the DOM from the component body. Event handlers are suitable for interaction-driven work, while Effects synchronize external systems after a committed render. Refs provide access to committed DOM nodes when an imperative operation is necessary.

21. What does it mean for a component to be pure?

A pure component calculates its output from its inputs without changing anything outside that calculation. Given the same props, state, and context, its rendering should produce the same result. It must not mutate received data, increment shared counters, make purchases, or subscribe to events while rendering.

For example, sorting a products prop with products.sort() changes the caller's array. Sorting a new copy instead leaves the input untouched. Mutating a temporary object or array created entirely within that render is allowed because no earlier render or outside code observes that object.

Purity applies to rendering, not to a requirement that the application never has side effects. Updating state in response to a click and synchronizing a connection in an Effect are legitimate. Keeping those operations outside rendering lets React repeat, pause, or abandon calculations safely. It also makes components easier to understand and test without depending on execution order.

22. How does data flow between parent and child components?

React normally uses one-way data flow: a parent passes values down through props, and children render from those values. When a child needs to request a change, the parent passes a callback that the child invokes. The parent then updates the state it owns and supplies new props on the next render.

For example, a product page can own quantity and pass both quantity and onQuantityChange to a quantity selector. The selector reports the user's chosen number rather than mutating a parent object. A price summary can receive the same quantity, keeping the display consistent.

The callback does not reverse the direction of state ownership; it communicates an event back to the owner. This explicit flow makes it easier to trace where a value came from and which action can change it. Context can carry values across intermediate components, but it does not remove the need to decide who owns each state value.

23. When should you lift state up?

Lift state up when multiple components need to read or coordinate the same changing value. Move that value to their closest suitable common ancestor, then pass the value and event callbacks to the children. This gives the shared information one authoritative owner rather than several copies that must be synchronized.

For example, a list and a details panel may both depend on the selected record. Their common parent can own selectedId; clicking a row updates it, and the details panel receives the corresponding record. Maintaining a separate selection in each child would allow the two views to disagree.

Lift only the state that needs coordination. A list row's temporary hover state usually does not belong at the application root. Keeping unrelated state near the component that uses it reduces coupling. If passing shared values through many uninvolved layers becomes awkward, consider composition or Context rather than automatically moving everything into global storage.

24. What are controlled and uncontrolled inputs?

A controlled input receives its current value from React, usually through state. A text input uses value and an onChange handler that updates that value; a checkbox uses checked. React is the source of truth for what the control displays.

An uncontrolled input keeps its current value in the DOM. React can provide an initial value with defaultValue or defaultChecked, and the application can read the result later through FormData or a ref. Changing a default prop is not the same as controlling every edit.

Controlled inputs are useful when other UI must respond immediately to editing. Uncontrolled inputs can be simpler when values are needed mainly at submission. Do not switch an input between these modes during its lifetime. Initialize a controlled text value to a string, such as '', rather than starting with undefined. A controlled field without an updating handler is effectively read-only unless explicitly designed that way.

25. How do you handle text, checkbox, and select inputs?

Text inputs and selects expose their selected value through event.target.value; checkboxes expose their Boolean selection through event.target.checked. A checkbox's value is not the same as whether it is checked. Keep the state shape aligned with those differences.

For existing state variables and setters inside a component, the JSX can be:

JSX example

<>
  <label>Name <input value={name} onChange={e => setName(e.target.value)} /></label>
  <label>
    <input type="checkbox" checked={subscribed}
      onChange={e => setSubscribed(e.target.checked)} />
    Receive updates
  </label>
  <label>Plan
    <select value={plan} onChange={e => setPlan(e.target.value)}>
      <option value="basic">Basic</option>
      <option value="team">Team</option>
    </select>
  </label>
</>
<>
  <label>Name <input value={name} onChange={e => setName(e.target.value)} /></label>
  <label>
    <input type="checkbox" checked={subscribed}
      onChange={e => setSubscribed(e.target.checked)} />
    Receive updates
  </label>
  <label>Plan
    <select value={plan} onChange={e => setPlan(e.target.value)}>
      <option value="basic">Basic</option>
      <option value="team">Team</option>
    </select>
  </label>
</>
<>
  <label>Name <input value={name} onChange={e => setName(e.target.value)} /></label>
  <label>
    <input type="checkbox" checked={subscribed}
      onChange={e => setSubscribed(e.target.checked)} />
    Receive updates
  </label>
  <label>Plan
    <select value={plan} onChange={e => setPlan(e.target.value)}>
      <option value="basic">Basic</option>
      <option value="team">Team</option>
    </select>
  </label>
</>

Initialize name and plan as strings and subscribed as a Boolean. In React, control the selected option through the select's value, not an option's selected attribute. A multiple select uses an array of values. Numeric input values also arrive as strings; convert and validate deliberately rather than assuming the DOM has supplied a number.

26. How do you submit a form without accidentally reloading the page?

For an event-handler-based form, attach onSubmit to the form and call event.preventDefault() synchronously before any asynchronous work. This prevents the browser's normal navigation or reload while preserving submission through both the submit button and the keyboard.

JSX example

export default function SearchForm({ onSearch }) {
  function handleSubmit(event) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    onSearch(String(data.get('query') ?? ''));
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>Search <input name="query" /></label>
      <button type="submit">Search</button>
    </form>
  );
}
export default function SearchForm({ onSearch }) {
  function handleSubmit(event) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    onSearch(String(data.get('query') ?? ''));
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>Search <input name="query" /></label>
      <button type="submit">Search</button>
    </form>
  );
}
export default function SearchForm({ onSearch }) {
  function handleSubmit(event) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    onSearch(String(data.get('query') ?? ''));
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>Search <input name="query" /></label>
      <button type="submit">Search</button>
    </form>
  );
}

Controls need names to participate in FormData. Give non-submit buttons inside the form type="button" so they do not accidentally submit it. For network submissions, also represent pending and failure states rather than assuming completion. Modern React additionally supports function-valued form action props; that is an alternative submission model, not a reason to mix an unrelated click handler into this example.

27. How do you style React components?

React web components can use normal CSS through className. A stylesheet can be global or organized through a build-system feature such as CSS Modules; React itself does not require a particular styling library. Utility classes and component styling libraries are other project-level choices.

Use inline style objects for values calculated from data. Property names are camelCase, and values are JavaScript strings or numbers, not a CSS declaration string:

JSX example

export default function ColorSwatch({ color }) {
  return (
    <span className="swatch" style={{ backgroundColor: color }}>
      Selected color: {color}
    </span>
  );
}
export default function ColorSwatch({ color }) {
  return (
    <span className="swatch" style={{ backgroundColor: color }}>
      Selected color: {color}
    </span>
  );
}
export default function ColorSwatch({ color }) {
  return (
    <span className="swatch" style={{ backgroundColor: color }}>
      Selected color: {color}
    </span>
  );
}

The swatch class can define spacing and borders in a stylesheet, while the inline value supplies the selected color. Most numeric length properties receive pixel units; unitless properties such as opacity are exceptions. Keep responsive rules and pseudo-classes in CSS rather than trying to express them as ordinary inline style properties. These web styling conventions are not interchangeable with React Native's styling system.

28. What are Hooks, and what are the Rules of Hooks?

Hooks are React functions that let function components use features such as state, context, refs, and synchronization. Custom Hooks combine existing Hooks into reusable logic and conventionally have names beginning with use.

For ordinary Hooks such as useState and useEffect, call them only at the top level of a function component or another custom Hook. Do not call them in conditions, loops, event handlers, ordinary utility functions, or after a conditional early return. React relies on a consistent call order to associate Hook state with the correct call.

The use API is a specific exception: it can read a resource inside a condition or loop, but it still must run inside a component or Hook and must not be wrapped in try/catch. That exception does not make conditional useState valid. Keep the React Hooks lint rules enabled so violations and missing Effect dependencies are caught during development.

29. What is useEffect intended for?

useEffect synchronizes a committed component with a system outside React. Examples include connecting to a subscription, updating a third-party widget, or attaching a browser event listener. The Effect describes how that external system should match the current props and state, and often returns cleanup that undoes the synchronization.

It is not a general-purpose place for every calculation or every operation after a state change. A filtered list or a full name can usually be calculated during rendering. A purchase caused by clicking Buy belongs in that click or submission handler, where the user's intent is explicit.

Effects run on the client, not during server rendering. Their execution is tied to committed UI and dependency changes, rather than simply to calling the component function. Ask what external system is being synchronized before adding one. If there is no such system, an Effect may introduce an unnecessary render cycle or a fragile chain of state updates.

30. How does an Effect dependency array affect synchronization?

The dependency array tells React which reactive values the Effect reads and therefore when its synchronization may need to restart. React compares each listed value with its previous value using Object.is.

With no dependency array, the Effect runs after every commit of that component. With an empty array, there are no reactive dependencies that trigger a restart. With [roomId], it starts after mounting and restarts when roomId changes: cleanup runs using the old setup's values before the new setup runs.

Include the props, state, and other reactive values used by the Effect rather than selecting dependencies to force a preferred schedule. A missing dependency can leave the external system synchronized to stale information. Fresh object or function identities can also cause repeated restarts. An empty array does not mean an absolute once-ever guarantee: remounting and development checks can run setup again. Fix the Effect's design rather than suppressing the dependency linter to hide a mismatch.

31. Why does an Effect sometimes need a cleanup function?

Cleanup reverses an Effect's ongoing work so an obsolete setup does not remain active. A subscription should unsubscribe, a timer should be cleared, and an attached event listener should be removed. React runs cleanup before replacing that Effect with a new setup and when the component is removed.

JSX example

import { useEffect, useState } from 'react';

export default function ActiveSeconds() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(timer);
  }, []);
  return <p>Active for {seconds} seconds</p>;
}
import { useEffect, useState } from 'react';

export default function ActiveSeconds() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(timer);
  }, []);
  return <p>Active for {seconds} seconds</p>;
}
import { useEffect, useState } from 'react';

export default function ActiveSeconds() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const timer = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(timer);
  }, []);
  return <p>Active for {seconds} seconds</p>;
}

Without cleanup, a removed timer component could leave its interval running. Cleanup is not limited to final unmounting, so it should mirror setup rather than represent an unrelated business event. Development Strict Mode may exercise a setup-cleanup-setup cycle to expose missing teardown. For asynchronous requests, also prevent obsolete results from replacing newer data; stopping a subscription and handling a response race are related but distinct concerns.

32. How is useRef different from useState?

Both useRef and useState retain information between renders, but they serve different purposes. State describes information that affects rendering; updating it with its setter requests a render. A ref is a stable object with a mutable current property, and changing that property does not request a render.

Use state for a visible count, selected item, or input value. Use a ref for an interval handle, a DOM node, or another mutable value needed by an event handler but not itself used to determine the displayed UI. If the screen must change when the value changes, a ref alone is insufficient.

Do not routinely read or write refs during rendering. Doing so can make the result depend on mutable information outside that render's state snapshot; predictable initialization is a limited exception. A ref is not a shortcut for avoiding correct state ownership, nor does mutating a state object through a ref make that mutation safe.

33. How can a component focus a DOM input?

Create a ref with useRef(null), attach it to the input's ref prop, and call the DOM node's focus() method from an appropriate event handler. React fills the ref after attaching the input, so the component does not need to search the document by ID.

JSX example

import { useRef } from 'react';

export default function SearchBox() {
  const inputRef = useRef(null);
  return (
    <>
      <label>Search <input ref={inputRef} type="search" /></label>
      <button type="button" onClick={() => inputRef.current?.focus()}>
        Focus search
      </button>
    </>
  );
}
import { useRef } from 'react';

export default function SearchBox() {
  const inputRef = useRef(null);
  return (
    <>
      <label>Search <input ref={inputRef} type="search" /></label>
      <button type="button" onClick={() => inputRef.current?.focus()}>
        Focus search
      </button>
    </>
  );
}
import { useRef } from 'react';

export default function SearchBox() {
  const inputRef = useRef(null);
  return (
    <>
      <label>Search <input ref={inputRef} type="search" /></label>
      <button type="button" onClick={() => inputRef.current?.focus()}>
        Focus search
      </button>
    </>
  );
}

The null check covers the period when no node is attached. Do not call focus() from the component body: rendering may occur before the DOM is committed and should not change browser state. If an interaction first reveals an input, focus it after that node is attached rather than assuming a state setter has synchronously created it. Move focus intentionally and avoid repeatedly stealing it from the user.

34. What is Context, and when is it useful?

Context lets an ancestor provide a value to descendants without forwarding it through every intermediate component. Common uses include a theme, locale, or a shared application service. A consuming component reads the nearest matching provider above it, not a provider elsewhere in the tree.

JSX example

import { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function ThemeLabel() {
  const theme = useContext(ThemeContext);
  return <p>Current theme: {theme}</p>;
}

export default function App() {
  return <ThemeContext value="dark"><ThemeLabel /></ThemeContext>;
}
import { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function ThemeLabel() {
  const theme = useContext(ThemeContext);
  return <p>Current theme: {theme}</p>;
}

export default function App() {
  return <ThemeContext value="dark"><ThemeLabel /></ThemeContext>;
}
import { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function ThemeLabel() {
  const theme = useContext(ThemeContext);
  return <p>Current theme: {theme}</p>;
}

export default function App() {
  return <ThemeContext value="dark"><ThemeLabel /></ThemeContext>;
}

The direct provider syntax shown here is supported in React 19. The default is a fallback when no provider exists above the consumer. Context itself does not store changing application state; a provider can supply state managed elsewhere. When the provider's value changes, consumers update. Prefer ordinary props or composition for a short, explicit relationship, and do not put every local interaction into one broadly shared Context.

35. What does StrictMode check during development?

Strict Mode enables extra development checks for a component subtree. It can invoke render-related logic an extra time to reveal impurity, rerun Effect setup and cleanup to expose missing teardown, and repeat callback-ref setup and cleanup to catch ref-management problems. It also warns about deprecated API use.

These checks do not mean production clicks are deliberately executed twice. Event handlers are not the target of the extra render check. Instead, a duplicated array item may reveal that rendering mutated a prop, while duplicated connections may reveal that an Effect never disconnects.

The exact checks depend on where Strict Mode is enabled. In particular, a Strict Mode boundary below a non-Strict root does not perform the extra initial Effect cycle that would replay child Effects without their parents. Treat repeated development work as a diagnostic signal, not a reason to add a ref that suppresses setup. Correct code should tolerate legitimate remounting and clean up resources reliably.

36. How do you display loading, empty, error, and success states?

Represent the request lifecycle explicitly, then render a meaningful branch for each state. A status such as loading, error, or success is usually clearer than several independent Booleans that can contradict one another. An empty result is normally a successful response containing no matching records, not an error.

For example, a search view can show โ€œSearchingโ€ฆโ€ while loading, a retryable failure message on error, โ€œNo matching projectsโ€ when a successful result has an empty array, and the list otherwise. If no search has started, an additional idle state can explain what the user should do.

Keep initial loading separate from background refreshing when retaining existing results improves continuity. Do not clear useful content merely because a refresh begins unless that matches the product's needs. Prevent an older request from overwriting a newer query's result, and keep sensitive server error details out of the visible message. Test each branch deliberately rather than only the populated success screen.

37. How can you make a basic React form accessible?

Start with semantic HTML: a real form, native controls, visible labels, and a submit button. Associate each label with its control, either by nesting the control inside it or by pairing htmlFor with an id. A placeholder is not a substitute for a persistent label.

Use useId when a reusable field needs unique IDs for its label, help text, or error text. Connect explanatory text with aria-describedby; indicate invalid fields with aria-invalid when validation has found an error. Explain the correction in text rather than relying on red styling alone.

Related controls can use fieldset and legend. Keep keyboard order natural, preserve visible focus indicators, and avoid making clickable non-interactive elements imitate buttons. After an unsuccessful submission, make the errors discoverable and manage focus deliberately when appropriate. Accessibility requires checking the actual interaction, including keyboard and assistive-technology behavior, not merely adding ARIA attributes to JSX.

38. How does React render text safely, and what changes with dangerouslySetInnerHTML?

When a string is rendered as ordinary JSX content, React treats it as text rather than parsing it as HTML. For example, a comment containing <strong>Hello</strong> appears with those characters instead of becoming a bold element. That protects this text-rendering path from interpreting submitted markup as executable content.

dangerouslySetInnerHTML deliberately bypasses that boundary and inserts HTML. Its value has the shape { __html: html }. Use it only when the HTML has a trustworthy origin or has passed through an appropriate, maintained sanitization process. Simply obtaining content from an API or storing it in a database does not make it safe.

Do not describe React as preventing every form of cross-site scripting. Unsafe raw HTML, unvalidated URL destinations, third-party DOM manipulation, and other injection paths still require controls. Prefer ordinary text or structured React elements when rich HTML is not necessary. Escaping text and sanitizing a permitted subset of HTML solve different problems.

39. What are React Developer Tools used for?

React Developer Tools helps inspect the application at the component level rather than only as a DOM tree. The Components panel shows the hierarchy and lets a developer inspect props, state, and other React information. This is useful for identifying which component owns a value and whether a child received the expected input.

The Profiler supports investigating rendering work during an interaction. It can help locate expensive components or repeated updates, providing evidence before introducing memoization or restructuring state. A useful debugging workflow is to reproduce one interaction, inspect the relevant component's inputs, and profile that interaction if the problem is performance-related.

Developer Tools complements the browser's Network, Console, and Performance panels. A slow request, expensive layout, or large image is not necessarily a React rendering problem. Editing a value in the inspection interface is a temporary debugging experiment, not a source-code fix or proof that the underlying data flow is correct.

40. How would you test a simple interactive React component?

Test observable behavior from the user's perspective. Render the component, find controls by accessible role or label, perform a realistic interaction, and assert the resulting UI. For a counter, verify the initial number, click Increment, and verify the changed number rather than checking its internal Hook implementation.

For an existing Counter component, a test body using React Testing Library can be:

JSX example

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';

// Inside an async test; expect comes from the configured test runner.
const user = userEvent.setup();
render(<Counter />);
await user.click(screen.getByRole('button', { name: 'Increment' }));
expect(screen.getByText('Count: 1')).toBeTruthy();
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';

// Inside an async test; expect comes from the configured test runner.
const user = userEvent.setup();
render(<Counter />);
await user.click(screen.getByRole('button', { name: 'Increment' }));
expect(screen.getByText('Count: 1')).toBeTruthy();
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';

// Inside an async test; expect comes from the configured test runner.
const user = userEvent.setup();
render(<Counter />);
await user.click(screen.getByRole('button', { name: 'Increment' }));
expect(screen.getByText('Count: 1')).toBeTruthy();

The expected text and button name must match the component's public behavior. Await interactions and asynchronous UI instead of inserting arbitrary sleeps. React Testing Library handles act around its helpers; lower-level React tests may need explicit awaited act calls. Add relevant boundary cases, such as a disabled control or repeated clicks, without coupling assertions to implementation details.

React Interview Questions and Answers for Intermediate

The intermediate answers focus on dependable implementation choices and their trade-offs. Examples distinguish correctness from optimization and clarify the boundaries between React features, browser behavior, and framework support.

41. How do you choose a state structure that avoids contradictory and duplicated values?

I would store the smallest set of facts that cannot be derived from other facts, then make invalid combinations difficult to represent. Two independent booleans such as isSaving and isSaved allow contradictory states; one status or a discriminated union communicates the permitted alternatives.

TypeScript state-model excerpt:

TypeScript example

type SaveState =
  | { status: 'idle' }
  | { status: 'saving' }
  | { status: 'saved'; version: number }
  | { status: 'error'; message: string };
type SaveState =
  | { status: 'idle' }
  | { status: 'saving' }
  | { status: 'saved'; version: number }
  | { status: 'error'; message: string };
type SaveState =
  | { status: 'idle' }
  | { status: 'saving' }
  | { status: 'saved'; version: number }
  | { status: 'error'; message: string };

For a selected item, I would usually store selectedId, not a second copy of the item object. Rendering can look up the current item, so editing the collection does not leave the selection stale. Counts, totals, and filtered lists are normally derived. Related fields that always change together can share an object; unrelated fields do not need one giant object. Finally, I would flatten deeply nested editable data where practical and define what happens if the selected record disappears.

42. When should you calculate a value during rendering instead of storing it in state?

Calculate a value during rendering when current props and state completely determine it. Storing that result separately creates another value to synchronize and often an avoidable render with stale information. An Effect is for synchronization with something outside React, not for maintaining a second copy of a calculation.

JSX example

function CartSummary({ lines }) {
  const totalCents = lines.reduce(
    (sum, line) => sum + line.priceCents * line.quantity,
    0
  );
  return <output>{(totalCents / 100).toFixed(2)}</output>;
}
function CartSummary({ lines }) {
  const totalCents = lines.reduce(
    (sum, line) => sum + line.priceCents * line.quantity,
    0
  );
  return <output>{(totalCents / 100).toFixed(2)}</output>;
}
function CartSummary({ lines }) {
  const totalCents = lines.reduce(
    (sum, line) => sum + line.priceCents * line.quantity,
    0
  );
  return <output>{(totalCents / 100).toFixed(2)}</output>;
}

There is no total setter because the line items are the source of truth. If profiling proves the calculation expensive, memoization may avoid repeating it when inputs are unchanged, but it does not change ownership. State is appropriate when the value represents independent history or user intent: an editable draft can deliberately differ from the latest server value. That distinction matters more than whether calculating the value takes one line or twenty.

43. How do component position, type, and key control state preservation?

React associates state with a component's identity in the render tree, not with a JSX variable or the location of its function call in source code. Keeping the same component type at the same position generally preserves state. Replacing the type or removing the component destroys that subtree's state. A key lets React distinguish identities within a parent instead of relying only on order.

Application excerpt; MessageEditor owns its draft:

JSX example

function Conversation({ recipient, MessageEditor }) {
  return <MessageEditor key={recipient.id} recipient={recipient} />;
}
function Conversation({ recipient, MessageEditor }) {
  return <MessageEditor key={recipient.id} recipient={recipient} />;
}
function Conversation({ recipient, MessageEditor }) {
  return <MessageEditor key={recipient.id} recipient={recipient} />;
}

Changing the recipient now intentionally resets the editor, preventing one person's draft from carrying into another conversation. Without the key, changing only recipient would normally preserve local state. Stable record keys also preserve the correct row state when a list reorders. Random keys force remounts, while index keys can attach state to the wrong record. I would define component functions at module scope; defining them inside a parent creates a new component type on every render.

44. How does useReducer organize complex state transitions?

useReducer centralizes related state transitions in a pure function. Event handlers describe what happened through actions; the reducer decides the next state from the previous state and that action. This is useful when several interactions modify the same fields or must preserve shared invariants.

JSX example

import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'reset': return { count: 0 };
    default: throw new Error('Unknown action');
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return <button onClick={() => dispatch({ type: 'increment' })}>
    {state.count}
  </button>;
}
import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'reset': return { count: 0 };
    default: throw new Error('Unknown action');
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return <button onClick={() => dispatch({ type: 'increment' })}>
    {state.count}
  </button>;
}
import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'reset': return { count: 0 };
    default: throw new Error('Unknown action');
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return <button onClick={() => dispatch({ type: 'increment' })}>
    {state.count}
  </button>;
}

The example is small, but the same approach can model a multi-step editor with explicit transitions. Reducers must not mutate existing state, fetch data, or generate nondeterministic identifiers. Perform side effects in the appropriate event or Effect and dispatch their outcomes. I would test important transitions as ordinary function calls; adopting a reducer does not automatically make state global or persistent.

45. How can Context and a reducer work together without becoming a global-state dumping ground?

A reducer defines the update rules; Context makes the resulting state and dispatch available to descendants without forwarding them through every intermediary. I would put the provider near the feature that owns the data, not automatically around the whole application.

Provider excerpt; reducer and initialState are supplied by the feature:

JSX example

import { createContext, useReducer } from 'react';

export const EditorState = createContext(null);
export const EditorDispatch = createContext(null);

export function EditorProvider({ reducer, initialState, children }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return <EditorState value={state}>
    <EditorDispatch value={dispatch}>{children}</EditorDispatch>
  </EditorState>;
}
import { createContext, useReducer } from 'react';

export const EditorState = createContext(null);
export const EditorDispatch = createContext(null);

export function EditorProvider({ reducer, initialState, children }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return <EditorState value={state}>
    <EditorDispatch value={dispatch}>{children}</EditorDispatch>
  </EditorState>;
}
import { createContext, useReducer } from 'react';

export const EditorState = createContext(null);
export const EditorDispatch = createContext(null);

export function EditorProvider({ reducer, initialState, children }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return <EditorState value={state}>
    <EditorDispatch value={dispatch}>{children}</EditorDispatch>
  </EditorState>;
}

Separate contexts let a component consume dispatch without subscribing to the changing state value, though they do not prevent every ordinary parent-driven render. I would keep transient input state local, separate unrelated domains, and give consumer Hooks clear missing-provider errors. Context is a distribution mechanism, not a cache, selector engine, or substitute for deciding ownership. Frequently changing large shared datasets may need a store with more selective subscriptions.

46. What does a custom Hook share, and what does it not share?

A custom Hook shares reusable stateful logic, not a single state instance. Each call gets its own state and Effects unless the Hook deliberately reads a shared Context, external store, or cache. Calling useToggle in two components therefore creates two independent toggles.

JSX example

import { useState } from 'react';

function useToggle(initialValue = false) {
  const [on, setOn] = useState(initialValue);
  function toggle() {
    setOn(previous => !previous);
  }
  return { on, toggle };
}

function NotificationsToggle() {
  const { on, toggle } = useToggle();
  return <button aria-pressed={on} onClick={toggle}>
    Notifications
  </button>;
}
import { useState } from 'react';

function useToggle(initialValue = false) {
  const [on, setOn] = useState(initialValue);
  function toggle() {
    setOn(previous => !previous);
  }
  return { on, toggle };
}

function NotificationsToggle() {
  const { on, toggle } = useToggle();
  return <button aria-pressed={on} onClick={toggle}>
    Notifications
  </button>;
}
import { useState } from 'react';

function useToggle(initialValue = false) {
  const [on, setOn] = useState(initialValue);
  function toggle() {
    setOn(previous => !previous);
  }
  return { on, toggle };
}

function NotificationsToggle() {
  const { on, toggle } = useToggle();
  return <button aria-pressed={on} onClick={toggle}>
    Notifications
  </button>;
}

I would name the Hook after its responsibility and expose inputs that explain when synchronization changes. A subscription Hook should own subscription cleanup; a fetching Hook should define races and error behavior. I would avoid generic lifecycle wrappers such as useMount, which conceal dependencies. If two components must stay synchronized, I would lift the state or introduce an explicit shared owner instead of expecting extraction into a Hook to connect them.

47. How do you diagnose stale closures in event handlers and Effects?

A closure captures values from the render in which it was created. That is often correct: a delayed confirmation may intentionally refer to the item selected when the user clicked. It becomes a stale-closure bug when the callback needs current values but keeps an older render's values.

For a timer that only increments state, use a functional updater:

JSX example

import { useEffect, useState } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setSeconds(n => n + 1), 1000);
    return () => clearInterval(id);
  }, []);
  return <output>{seconds}</output>;
}
import { useEffect, useState } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setSeconds(n => n + 1), 1000);
    return () => clearInterval(id);
  }, []);
  return <output>{seconds}</output>;
}
import { useEffect, useState } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setSeconds(n => n + 1), 1000);
    return () => clearInterval(id);
  }, []);
  return <output>{seconds}</output>;
}

I would inspect missing Effect or callback dependencies and ask whether the operation needs the original snapshot or the latest committed value. Functional updates solve previous-state calculations, not arbitrary stale props. Reactive inputs belong in dependencies. For genuinely non-reactive notifications originating in Effects, an Effect Event can read current values without restarting synchronization. Suppressing the dependency linter usually hides the underlying ownership decision.

48. How do you fix an Effect that keeps rerunning in a loop?

An Effect loop usually has two parts: the Effect updates state, and that update changes one of the Effect's dependencies. I would first ask whether the Effect is needed at all. Derived UI data should usually be calculated while rendering, and user-triggered work should usually stay in its event handler.

For a necessary connection, construct temporary options inside the Effect rather than depending on a new object from every render:

JSX example

import { useEffect } from 'react';

function Connection({ roomId, connect }) {
  useEffect(() => {
    const disconnect = connect({ roomId });
    return disconnect;
  }, [roomId, connect]);
  return null;
}
import { useEffect } from 'react';

function Connection({ roomId, connect }) {
  useEffect(() => {
    const disconnect = connect({ roomId });
    return disconnect;
  }, [roomId, connect]);
  return null;
}
import { useEffect } from 'react';

function Connection({ roomId, connect }) {
  useEffect(() => {
    const disconnect = connect({ roomId });
    return disconnect;
  }, [roomId, connect]);
  return null;
}

Here, connect is an application-provided function whose identity should be stable when its behavior is unchanged. I would compare dependencies with Object.is, inspect state writes, and move non-reactive constants out of the component. Memoization can occasionally help identity-sensitive APIs, but deleting dependencies is not a repair. StrictMode's extra development setup-and-cleanup cycle is finite and should not be confused with an actual infinite loop.

49. How do you safely fetch data in an Effect without showing an outdated response?

I would associate each response with the request that produced it and prevent obsolete work from committing. Aborting a request saves resources; a cleanup flag also protects against asynchronous work that finishes despite cancellation. Errors need the same stale-response guard as successful results.

Effect excerpt inside a component importing useEffect; setResult is its state setter:

JSX example

useEffect(() => {
  let active = true;
  const controller = new AbortController();
  setResult({ status: 'loading' });
  async function load() {
    try {
      const response = await fetch(`/api/items?q=${encodeURIComponent(query)}`, {
        signal: controller.signal
      });
      if (!response.ok) throw new Error('Request failed');
      const items = await response.json();
      if (active) setResult({ status: 'success', items });
    } catch (error) {
      if (active) setResult({ status: 'error', error });
    }
  }
  load();
  return () => { active = false; controller.abort(); };
}, [query]);
useEffect(() => {
  let active = true;
  const controller = new AbortController();
  setResult({ status: 'loading' });
  async function load() {
    try {
      const response = await fetch(`/api/items?q=${encodeURIComponent(query)}`, {
        signal: controller.signal
      });
      if (!response.ok) throw new Error('Request failed');
      const items = await response.json();
      if (active) setResult({ status: 'success', items });
    } catch (error) {
      if (active) setResult({ status: 'error', error });
    }
  }
  load();
  return () => { active = false; controller.abort(); };
}, [query]);
useEffect(() => {
  let active = true;
  const controller = new AbortController();
  setResult({ status: 'loading' });
  async function load() {
    try {
      const response = await fetch(`/api/items?q=${encodeURIComponent(query)}`, {
        signal: controller.signal
      });
      if (!response.ok) throw new Error('Request failed');
      const items = await response.json();
      if (active) setResult({ status: 'success', items });
    } catch (error) {
      if (active) setResult({ status: 'error', error });
    }
  }
  load();
  return () => { active = false; controller.abort(); };
}, [query]);

For a real interface, I would decide whether old results remain visible while loading and label them accordingly. Cancellation does not undo a server mutation. Shared caching, retries, and invalidation are separate requirements, often better handled by a query layer.

50. Why might a framework or query library be better than fetching every resource in Effects?

Fetching separately in every component Effect makes the component tree determine request timing. A parent may fetch before rendering a child that starts another request, producing a waterfall. Effects also do not run during server rendering, and repeated mounts can repeat work unless caching is added.

A framework's route or server data layer can start related requests earlier and coordinate rendering, navigation, and server delivery. A client query library can provide shared request identity, caching, deduplication, freshness rules, retries, and mutation invalidation. Those are capabilities of the chosen framework or library, not automatic features of useEffect or React itself.

I would choose based on the application's rendering and ownership model rather than adding both layers indiscriminately. Cache keys must include inputs such as filters and tenant scope. Retry policy should distinguish a temporary network failure from invalid credentials or validation errors. For a small isolated client-only resource, a carefully cleaned-up Effect may still be enough; the trade-off changes when several screens share and mutate the same data.

51. How do useEffect and useLayoutEffect differ?

Both Hooks synchronize with external systems after React commits, but useLayoutEffect runs before the browser repaints and blocks painting while its work and immediate state updates finish. That makes it appropriate for measurements that must affect the first visible layout, such as positioning a tooltip without a flash.

Measurement example:

JSX example

import { useLayoutEffect, useRef, useState } from 'react';

function MeasuredLabel({ children }) {
  const ref = useRef(null);
  const [width, setWidth] = useState(0);
  useLayoutEffect(() => {
    setWidth(ref.current.getBoundingClientRect().width);
  }, [children]);
  return <div><span ref={ref}>{children}</span><output>{width}px</output></div>;
}
import { useLayoutEffect, useRef, useState } from 'react';

function MeasuredLabel({ children }) {
  const ref = useRef(null);
  const [width, setWidth] = useState(0);
  useLayoutEffect(() => {
    setWidth(ref.current.getBoundingClientRect().width);
  }, [children]);
  return <div><span ref={ref}>{children}</span><output>{width}px</output></div>;
}
import { useLayoutEffect, useRef, useState } from 'react';

function MeasuredLabel({ children }) {
  const ref = useRef(null);
  const [width, setWidth] = useState(0);
  useLayoutEffect(() => {
    setWidth(ref.current.getBoundingClientRect().width);
  }, [children]);
  return <div><span ref={ref}>{children}</span><output>{width}px</output></div>;
}

This measures on the specified content changes, not every resize; continuous size tracking would need an observer. I would normally use useEffect for subscriptions and network synchronization. It generally lets painting proceed, although interaction-related timing means โ€œalways after paintโ€ is too strong. Neither Effect runs during server rendering. Layout Effects should be small because moving ordinary work into them can make the entire interface feel slower.

52. What is useEffectEvent, and when should it be used?

useEffectEvent defines a non-reactive event within Effect logic. Its callback reads the latest committed props and state without requiring those values to restart the Effect. It is useful when a subscription or timer has a stable lifecycle but its notification needs current display settings or callbacks.

JSX example

import { useEffect, useEffectEvent } from 'react';

function Heartbeat({ intervalMs, onBeat }) {
  const notify = useEffectEvent(() => onBeat());
  useEffect(() => {
    const id = setInterval(() => notify(), intervalMs);
    return () => clearInterval(id);
  }, [intervalMs]);
  return null;
}
import { useEffect, useEffectEvent } from 'react';

function Heartbeat({ intervalMs, onBeat }) {
  const notify = useEffectEvent(() => onBeat());
  useEffect(() => {
    const id = setInterval(() => notify(), intervalMs);
    return () => clearInterval(id);
  }, [intervalMs]);
  return null;
}
import { useEffect, useEffectEvent } from 'react';

function Heartbeat({ intervalMs, onBeat }) {
  const notify = useEffectEvent(() => onBeat());
  useEffect(() => {
    const id = setInterval(() => notify(), intervalMs);
    return () => clearInterval(id);
  }, [intervalMs]);
  return null;
}

Changing intervalMs restarts the timer; changing onBeat changes what the next notification calls without restarting it. Effect Events are excluded from dependency arrays, and their function identity is not stable. They must be invoked from Effects or other Effect Events in the same component, including callbacks installed by those Effects. They are not general-purpose click handlers and should not be passed to other components or Hooks. I would never use one merely to hide a dependency that should genuinely resynchronize the external system.

53. When does useMemo help, and why is it not a correctness guarantee?

useMemo helps when a pure calculation is measurably expensive and its inputs often remain unchanged, or when a stable computed value enables another useful optimization. React compares dependencies with Object.is and reuses the previous result when they match.

JSX example

import { useMemo } from 'react';

function Totals({ transactions }) {
  const total = useMemo(
    () => transactions.reduce((sum, item) => sum + item.cents, 0),
    [transactions]
  );
  return <output>{total}</output>;
}
import { useMemo } from 'react';

function Totals({ transactions }) {
  const total = useMemo(
    () => transactions.reduce((sum, item) => sum + item.cents, 0),
    [transactions]
  );
  return <output>{total}</output>;
}
import { useMemo } from 'react';

function Totals({ transactions }) {
  const total = useMemo(
    () => transactions.reduce((sum, item) => sum + item.cents, 0),
    [transactions]
  );
  return <output>{total}</output>;
}

This only helps if the array retains its identity when its contents do not change and the avoided work justifies the bookkeeping. I would not mutate that array to keep its identity: doing so can reuse a stale result. React may discard a memoized cache, including when an initial render suspends, so application correctness must not depend on retention. Persistent user data belongs in state; imperative instance data may belong in a ref. The calculation must remain pure, including under development checks that invoke it more than once.

54. When does useCallback help preserve a useful function identity?

useCallback preserves a function reference while its dependencies remain equal. The benefit appears only when some consumer cares about identityโ€”for example, a memoized expensive child or an API whose subscription depends on the callback. It does not make the function's execution faster, and React does not invoke the function for you.

Component-body excerpt importing useCallback and useState:

JSX example

const [selectedIds, setSelectedIds] = useState([]);
const select = useCallback(id => {
  setSelectedIds(ids => ids.includes(id) ? ids : [...ids, id]);
}, []);
// Pass `select` to an identity-sensitive child as onSelect={select}.
const [selectedIds, setSelectedIds] = useState([]);
const select = useCallback(id => {
  setSelectedIds(ids => ids.includes(id) ? ids : [...ids, id]);
}, []);
// Pass `select` to an identity-sensitive child as onSelect={select}.
const [selectedIds, setSelectedIds] = useState([]);
const select = useCallback(id => {
  setSelectedIds(ids => ids.includes(id) ? ids : [...ids, id]);
}, []);
// Pass `select` to an identity-sensitive child as onSelect={select}.

The functional updater removes the need to capture selectedIds, while preserving correct updates. If the callback also reads a prop, that prop normally belongs in its dependencies. An empty array is not permission to freeze old behavior. I would avoid wrapping every handler reflexively: inline functions are often fine, and a single always-new prop can defeat the intended child optimization. When React Compiler is enabled, I would first check what it already optimizes.

55. What does memo skip, and what can still make a memoized component render?

memo can skip a component render caused by its parent when its props compare equal. By default, it compares each prop with Object.is; it does not recursively compare object contents. A freshly created array, object, or function therefore commonly prevents the skip.

JSX example

import { memo } from 'react';

export const Price = memo(function Price({ cents }) {
  return <span>{(cents / 100).toFixed(2)}</span>;
});
import { memo } from 'react';

export const Price = memo(function Price({ cents }) {
  return <span>{(cents / 100).toFixed(2)}</span>;
});
import { memo } from 'react';

export const Price = memo(function Price({ cents }) {
  return <span>{(cents / 100).toFixed(2)}</span>;
});

Passing the same numeric cents makes this component eligible to skip an ordinary parent-driven render, although a component this cheap may not need memoization. Its own state updates and changes to Context it consumes can still render it. memo is a performance optimization, not a promise that React will never call the component again.

A custom comparator must account for every prop that affects output or behavior, including callbacks. Ignoring a changed function can preserve a closure with outdated state. Deep comparisons can cost more than rendering and become dangerous as data grows. I would measure a representative interaction before and after the change.

56. How does React Compiler change manual memoization decisions?

React Compiler moves much memoization from handwritten Hooks and wrappers into the build step. For code it successfully compiles, it can reuse component work, values, and functions when their relevant inputs have not changed. Merely upgrading the React runtime does not mean the application's build has enabled the compiler.

I would first verify compiler configuration and diagnostics, then profile the actual slow interaction. In compiled code, I would generally write straightforward pure components instead of adding memo, useMemo, and useCallback everywhere. Existing manual memoization should not be deleted across a codebase without checking behavior and performance, especially around integrations that observe identity.

The compiler does not eliminate the need for sound state ownership, immutable updates, or the Rules of React. It cannot make a huge initial calculation free, reduce a large network payload, or remove the browser's cost of laying out thousands of nodes. Optimization remains evidence-driven; the difference is that manual memoization becomes a targeted tool rather than a default coding style.

57. How does React batch state updates?

React batches multiple state updates so it can process them together instead of committing a partially updated interface after each setter. Modern React roots also automatically batch updates originating in places such as promise callbacks and timers, not only React event handlers. Batching does not turn a setter into an immediate assignment.

JSX example

import { useState } from 'react';

function AddThree() {
  const [count, setCount] = useState(0);
  function increment() {
    setCount(n => n + 1);
    setCount(n => n + 1);
    setCount(n => n + 1);
  }
  return <button onClick={increment}>{count}</button>;
}
import { useState } from 'react';

function AddThree() {
  const [count, setCount] = useState(0);
  function increment() {
    setCount(n => n + 1);
    setCount(n => n + 1);
    setCount(n => n + 1);
  }
  return <button onClick={increment}>{count}</button>;
}
import { useState } from 'react';

function AddThree() {
  const [count, setCount] = useState(0);
  function increment() {
    setCount(n => n + 1);
    setCount(n => n + 1);
    setCount(n => n + 1);
  }
  return <button onClick={increment}>{count}</button>;
}

The queued updater functions compose to add three. Calling setCount(count + 1) three times would instead request the same replacement value three times because count is the current render's snapshot. React keeps separate intentional interactions safe, and arbitrary asynchronous boundaries are not one guaranteed transaction. I would not depend on an exact render count. flushSync is an exceptional integration escape hatch, not a normal way to read the next state.

58. What does useTransition do for an interaction?

useTransition marks some updates as non-urgent and exposes isPending. React can interrupt the resulting background rendering to handle urgent work such as typing. The callback starts immediately; a Transition is not a timer, worker thread, or cancellation mechanism for network requests.

Component-body excerpt importing useState and useTransition:

JSX example

const [tab, setTab] = useState('overview');
const [isPending, startTransition] = useTransition();
function selectTab(nextTab) {
  startTransition(() => setTab(nextTab));
}
// Render the selected panel from `tab` and pending feedback from `isPending`.
const [tab, setTab] = useState('overview');
const [isPending, startTransition] = useTransition();
function selectTab(nextTab) {
  startTransition(() => setTab(nextTab));
}
// Render the selected panel from `tab` and pending feedback from `isPending`.
const [tab, setTab] = useState('overview');
const [isPending, startTransition] = useTransition();
function selectTab(nextTab) {
  startTransition(() => setTab(nextTab));
}
// Render the selected panel from `tab` and pending feedback from `isPending`.

A tab switch that triggers expensive rendering is a useful candidate. Controlled text inputs should keep their own value updates urgent; defer the expensive dependent content instead. A Transition can also help retain already revealed Suspense content while its replacement loads. Async Actions are supported, but state updates after an await currently need another startTransition to be marked as Transitions. Request ordering, stale responses, and duplicate writes still need explicit handling; scheduling priority does not establish business correctness.

59. How is useDeferredValue different from debouncing?

useDeferredValue lets one part of the UI temporarily use an older value while React attempts the newer rendering in the background. Debouncing waits for a chosen quiet interval before doing work. Deferral has no fixed delay: a fast device may catch up quickly, while expensive rendering can be interrupted by newer input.

Component-body excerpt importing useDeferredValue and useState:

JSX example

const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const showingStaleResults = query !== deferredQuery;
// Input uses query; an optimized results subtree uses deferredQuery.
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const showingStaleResults = query !== deferredQuery;
// Input uses query; an optimized results subtree uses deferredQuery.
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const showingStaleResults = query !== deferredQuery;
// Input uses query; an optimized results subtree uses deferredQuery.

To avoid rerendering an expensive subtree with the unchanged deferred value during the urgent pass, that subtree needs effective memoization or compiler optimization. The UI should communicate when visible results lag behind the input. Deferral does not inherently reduce network requests; a search may still request every new query. I would debounce or throttle requests separately if the product and API require that behavior. It is also not a correctness guard against out-of-order responses, which the data layer must handle independently.

60. How do lazy and Suspense support code splitting?

lazy creates a component whose code is loaded when React first tries to render it. A dynamic import gives a compatible bundler a code-splitting point. While the module is pending, a surrounding Suspense boundary renders its fallback.

JSX example

import { lazy, Suspense } from 'react';

// Report.jsx must default-export a React component.
const Report = lazy(() => import('./Report.jsx'));

export function ReportPanel() {
  return <Suspense fallback={<p role="status">Loading reportโ€ฆ</p>}>
    <Report />
  </Suspense>;
}
import { lazy, Suspense } from 'react';

// Report.jsx must default-export a React component.
const Report = lazy(() => import('./Report.jsx'));

export function ReportPanel() {
  return <Suspense fallback={<p role="status">Loading reportโ€ฆ</p>}>
    <Report />
  </Suspense>;
}
import { lazy, Suspense } from 'react';

// Report.jsx must default-export a React component.
const Report = lazy(() => import('./Report.jsx'));

export function ReportPanel() {
  return <Suspense fallback={<p role="status">Loading reportโ€ฆ</p>}>
    <Report />
  </Suspense>;
}

I would declare the lazy component at module scope so its identity does not change on every parent render. The loader must resolve to a module-like object with a valid component in default; React caches the loader's promise and result. A rejected import needs an error boundary, because Suspense handles waiting rather than presenting a failure UI.

Good split points are meaningful routes or heavy optional features. Splitting every small component can add request and fallback overhead. I would verify emitted bundles and loading behavior rather than assuming lazy alone guarantees a smaller useful initial download.

61. What kinds of asynchronous work can a Suspense boundary handle?

Suspense coordinates rendering that cannot yet finish; it is not a general detector of asynchronous JavaScript. Typical sources are a lazy component and a cached Promise read through use, including data supplied by an RSC-capable framework. Starting fetch in an Effect or event handler does not make a boundary show its fallback automatically.

Current React also coordinates supported rendering resources, such as stylesheets with precedence, and streaming server HTML. Fonts and images are not universally awaited: their special waiting behavior applies when a View Transition animates a boundary's reveal. I would not infer that every <img> becomes a Suspense data source.

For data, I would use a supported framework integration or a deliberately managed stable Promise rather than create a fresh Promise on each render. Boundary placement defines which content reveals together, so it should follow the screen's loading experience, not every component boundary. Failed Promise reads need an error boundary. Suspense also does not replace pending feedback for a mutation that leaves the current content usable.

62. What is an error boundary, and what errors does it not catch?

An error boundary isolates a descendant rendering failure and replaces that subtree with a fallback. In React's built-in component API, it is implemented with a class using getDerivedStateFromError, with componentDidCatch available for reporting. A library can package this behind a convenient component API.

JSX example

import { Component } from 'react';

export class SectionBoundary extends Component {
  state = { failed: false };
  static getDerivedStateFromError() {
    return { failed: true };
  }
  render() {
    return this.state.failed
      ? <p role="alert">This section could not load.</p>
      : this.props.children;
  }
}
import { Component } from 'react';

export class SectionBoundary extends Component {
  state = { failed: false };
  static getDerivedStateFromError() {
    return { failed: true };
  }
  render() {
    return this.state.failed
      ? <p role="alert">This section could not load.</p>
      : this.props.children;
  }
}
import { Component } from 'react';

export class SectionBoundary extends Component {
  state = { failed: false };
  static getDerivedStateFromError() {
    return { failed: true };
  }
  render() {
    return this.state.failed
      ? <p role="alert">This section could not load.</p>
      : this.props.children;
  }
}

It does not catch errors in its own rendering, ordinary event handlers, arbitrary timer callbacks, or server rendering through the client boundary mechanism. An important exception to the asynchronous caveat is an error thrown inside an Action passed to useTransition's startTransition, which can reach an error boundary. Expected validation and request failures often deserve explicit local error state instead. I would define a genuine retry or reset path and place boundaries around recoverable user-facing sections, not every tiny element.

63. How do portals affect DOM placement, Context, and event propagation?

A portal changes where DOM nodes are placed without changing their position in the React ownership tree. Its children still receive Context from their React ancestors, and React events propagate through that React tree rather than following only DOM ancestry.

JSX example

import { createPortal } from 'react-dom';

function Notice({ target, onActivate }) {
  return <section onClick={onActivate}>
    {createPortal(<button>Activate notice</button>, target)}
  </section>;
}
import { createPortal } from 'react-dom';

function Notice({ target, onActivate }) {
  return <section onClick={onActivate}>
    {createPortal(<button>Activate notice</button>, target)}
  </section>;
}
import { createPortal } from 'react-dom';

function Notice({ target, onActivate }) {
  return <section onClick={onActivate}>
    {createPortal(<button>Activate notice</button>, target)}
  </section>;
}

Even if target is outside the section in the DOM, clicking the button invokes the section's React click handler. The target must already exist; supplying a different target recreates the portal content. I would stop propagation deliberately or restructure ownership if this bubbling is unwanted, rather than assuming a portal isolates events.

Portals are useful for overlays that must escape clipping or stacking constraints. They do not automatically create an accessible modal: labeling, focus placement, trapping where appropriate, Escape behavior, background interaction, and focus restoration remain separate responsibilities. In server-rendered applications, the portal target also needs a browser-safe lifecycle.

64. How do you expose a focused imperative API with refs and useImperativeHandle?

useImperativeHandle lets a component expose a small handle instead of its complete DOM node or internal implementation. I would use it for inherently imperative capabilities such as focusing, selecting text, or scrolling, while expressing ordinary visual state through props.

React 19 function-component example:

JSX example

import { useImperativeHandle, useRef } from 'react';

function SearchField({ ref, ...props }) {
  const inputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    focus() { inputRef.current?.focus(); },
    select() { inputRef.current?.select(); }
  }), []);
  return <input {...props} ref={inputRef} />;
}
import { useImperativeHandle, useRef } from 'react';

function SearchField({ ref, ...props }) {
  const inputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    focus() { inputRef.current?.focus(); },
    select() { inputRef.current?.select(); }
  }), []);
  return <input {...props} ref={inputRef} />;
}
import { useImperativeHandle, useRef } from 'react';

function SearchField({ ref, ...props }) {
  const inputRef = useRef(null);
  useImperativeHandle(ref, () => ({
    focus() { inputRef.current?.focus(); },
    select() { inputRef.current?.select(); }
  }), []);
  return <input {...props} ref={inputRef} />;
}

A parent can create its own ref, pass it to SearchField, and call ref.current?.focus() from an event or Effect. It cannot use this handle to mutate arbitrary styling because those details are not exposed. React 19 permits receiving ref as a prop; older React versions require forwardRef for this pattern. If exposed methods capture reactive values, the dependency list must include them. I would avoid an imperative open()/close() API when an open prop more clearly represents ownership.

65. How does useId help connect accessible controls and descriptions?

useId creates a component-associated identifier that can connect a label, input, hint, and error without hard-coded IDs colliding when the component appears multiple times. It also supports server/client matching when the rendered component trees agree.

JSX example

import { useId } from 'react';

function AccountName() {
  const id = useId();
  return <div>
    <label htmlFor={id}>Account name</label>
    <input id={id} aria-describedby={`${id}-hint`} />
    <p id={`${id}-hint`}>Use the name shown on your account.</p>
  </div>;
}
import { useId } from 'react';

function AccountName() {
  const id = useId();
  return <div>
    <label htmlFor={id}>Account name</label>
    <input id={id} aria-describedby={`${id}-hint`} />
    <p id={`${id}-hint`}>Use the name shown on your account.</p>
  </div>;
}
import { useId } from 'react';

function AccountName() {
  const id = useId();
  return <div>
    <label htmlFor={id}>Account name</label>
    <input id={id} aria-describedby={`${id}-hint`} />
    <p id={`${id}-hint`}>Use the name shown on your account.</p>
  </div>;
}

One generated prefix can serve several related elements. An error can be included in aria-describedby when present, with aria-invalid indicating invalid input. The generated ID supplies the relationship, not the accessibility behavior by itself: labels still need meaningful text and errors need appropriate presentation.

I would not use useId for list keys, database identifiers, or resource-cache keys. Those identities should come from the data. With multiple independently hydrated roots, configured prefixes must remain consistent between the server and client. Random IDs generated during rendering are not an equivalent hydration-safe replacement.

66. How do you type component props, events, and refs with TypeScript?

I would type the component's public contract explicitly, use the relevant DOM element type for events and refs, and let TypeScript infer straightforward inline handlers. JSX-bearing TypeScript files use .tsx; the project also needs compatible React type definitions.

TSX example

import type { ChangeEvent, Ref } from 'react';

type NameFieldProps = {
  value: string;
  onValueChange: (value: string) => void;
  ref?: Ref<HTMLInputElement>;
};

function NameField({ value, onValueChange, ref }: NameFieldProps) {
  function handleChange(event: ChangeEvent<HTMLInputElement>) {
    onValueChange(event.currentTarget.value);
  }
  return <input aria-label="Name" ref={ref}
    value={value} onChange={handleChange} />;
}
import type { ChangeEvent, Ref } from 'react';

type NameFieldProps = {
  value: string;
  onValueChange: (value: string) => void;
  ref?: Ref<HTMLInputElement>;
};

function NameField({ value, onValueChange, ref }: NameFieldProps) {
  function handleChange(event: ChangeEvent<HTMLInputElement>) {
    onValueChange(event.currentTarget.value);
  }
  return <input aria-label="Name" ref={ref}
    value={value} onChange={handleChange} />;
}
import type { ChangeEvent, Ref } from 'react';

type NameFieldProps = {
  value: string;
  onValueChange: (value: string) => void;
  ref?: Ref<HTMLInputElement>;
};

function NameField({ value, onValueChange, ref }: NameFieldProps) {
  function handleChange(event: ChangeEvent<HTMLInputElement>) {
    onValueChange(event.currentTarget.value);
  }
  return <input aria-label="Name" ref={ref}
    value={value} onChange={handleChange} />;
}

This React 19 example types a ref prop without exposing implementation-specific state. A parent-created DOM ref would commonly use useRef<HTMLInputElement>(null) and account for its nullable current value. ReactNode fits broadly renderable children, while a narrower element type may suit APIs requiring an element. I would prefer discriminated unions for mutually exclusive prop modes and avoid any. Types check declared shapes, not untrusted JSON at runtime, so external data still needs validation.

67. How do you design reusable components without creating excessive prop complexity?

I would start from a coherent responsibility and a few real usage cases, not from every variation the component might someday support. A component with many interacting booleans often encodes several different components and permits combinations that make no sense.

For example, a notification can expose a tone value and a composition slot for actions rather than separate isDanger, isWarning, showRetry, and showDismiss flags. A layout component can accept children, a heading, and an optional footer instead of knowing every business entity rendered inside it. When modes genuinely have different requirements, separate components or a discriminated union can make invalid combinations unrepresentable.

I would clearly choose controlled or uncontrolled ownership, preserve useful native HTML attributes, and document defaults and accessibility responsibilities. Composition is not an excuse for an undocumented collection of slots. The API should make common cases easy while keeping exceptional cases explicit. Finally, I would test realistic combinations and remove props that merely leak an internal styling or state-management decision.

68. How do form Actions and useActionState manage submission state?

A function passed to a form's action receives submitted FormData and runs in an Action context. useActionState adds a result state and pending flag; its function receives the previous result first, then the submission payload. The returned dispatch function goes on the form.

JSX example

import { useActionState, useState } from 'react';

function NameForm({ save }) {
  const [name, setName] = useState('');
  const [message, submit, pending] = useActionState(async (_, data) => {
    try {
      await save(String(data.get('name') ?? ''));
      return 'Saved';
    } catch {
      return 'Could not save; please retry.';
    }
  }, '');
  return <form action={submit}>
    <label>Name <input name="name" value={name}
      onChange={e => setName(e.target.value)} /></label>
    <button disabled={pending}>Save</button>
    <p role="status">{message}</p>
  </form>;
}
import { useActionState, useState } from 'react';

function NameForm({ save }) {
  const [name, setName] = useState('');
  const [message, submit, pending] = useActionState(async (_, data) => {
    try {
      await save(String(data.get('name') ?? ''));
      return 'Saved';
    } catch {
      return 'Could not save; please retry.';
    }
  }, '');
  return <form action={submit}>
    <label>Name <input name="name" value={name}
      onChange={e => setName(e.target.value)} /></label>
    <button disabled={pending}>Save</button>
    <p role="status">{message}</p>
  </form>;
}
import { useActionState, useState } from 'react';

function NameForm({ save }) {
  const [name, setName] = useState('');
  const [message, submit, pending] = useActionState(async (_, data) => {
    try {
      await save(String(data.get('name') ?? ''));
      return 'Saved';
    } catch {
      return 'Could not save; please retry.';
    }
  }, '');
  return <form action={submit}>
    <label>Name <input name="name" value={name}
      onChange={e => setName(e.target.value)} /></label>
    <button disabled={pending}>Save</button>
    <p role="status">{message}</p>
  </form>;
}

Here save is an application-provided async function, not a React API. The controlled field retains its draft; successfully completed form Actions otherwise reset uncontrolled fields. Expected failures can be returned as state, while thrown failures reach an error boundary. Calls for one useActionState instance are queued sequentially. These client APIs do not require Server Functions; server execution requires an appropriate framework integration.

69. How does useFormStatus read the status of a parent form?

useFormStatus reads submission status from a parent form in the React tree. It belongs in a component rendered inside that form, not in the component that merely returns the form. Its pending value is useful for a reusable submit button that should not receive pending state through props.

JSX example

import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending}>
    {pending ? 'Sendingโ€ฆ' : 'Send'}
  </button>;
}

function MessageForm({ sendAction }) {
  return <form action={sendAction}>
    <label>Message <input name="message" /></label>
    <SubmitButton />
  </form>;
}
import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending}>
    {pending ? 'Sendingโ€ฆ' : 'Send'}
  </button>;
}

function MessageForm({ sendAction }) {
  return <form action={sendAction}>
    <label>Message <input name="message" /></label>
    <SubmitButton />
  </form>;
}
import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending}>
    {pending ? 'Sendingโ€ฆ' : 'Send'}
  </button>;
}

function MessageForm({ sendAction }) {
  return <form action={sendAction}>
    <label>Message <input name="message" /></label>
    <SubmitButton />
  </form>;
}

The Hook also exposes the submitted FormData, method, and action information. That can support feedback about the submission in progress, rather than reading whatever the user has since typed. It does not return the Action's result or validation errors; useActionState or application state handles those. It also does not track every unrelated request on the page. The important troubleshooting check is structural: the consuming component must actually be beneath the form whose status it needs.

70. How does useOptimistic model a temporary UI update?

useOptimistic overlays a temporary value on confirmed state while an Action is pending. In current React, its reducer is optional; without one, the setter accepts a value or updater. Call the setter inside an Action, not during rendering or an ordinary unwrapped event.

JSX example

import { useOptimistic, useState, useTransition } from 'react';

function LikeButton({ saveLike }) {
  const [liked, setLiked] = useState(false);
  const [optimistic, setOptimistic] = useOptimistic(liked);
  const [pending, startTransition] = useTransition();
  const [error, setError] = useState('');
  function like() {
    setError('');
    startTransition(async () => {
      setOptimistic(true);
      try {
        const confirmed = await saveLike();
        startTransition(() => setLiked(confirmed));
      } catch {
        setError('Could not save your like.');
      }
    });
  }
  return <><button disabled={pending || liked} onClick={like}>
    {optimistic ? 'Liked' : 'Like'}
  </button><p role="status">{error}</p></>;
}
import { useOptimistic, useState, useTransition } from 'react';

function LikeButton({ saveLike }) {
  const [liked, setLiked] = useState(false);
  const [optimistic, setOptimistic] = useOptimistic(liked);
  const [pending, startTransition] = useTransition();
  const [error, setError] = useState('');
  function like() {
    setError('');
    startTransition(async () => {
      setOptimistic(true);
      try {
        const confirmed = await saveLike();
        startTransition(() => setLiked(confirmed));
      } catch {
        setError('Could not save your like.');
      }
    });
  }
  return <><button disabled={pending || liked} onClick={like}>
    {optimistic ? 'Liked' : 'Like'}
  </button><p role="status">{error}</p></>;
}
import { useOptimistic, useState, useTransition } from 'react';

function LikeButton({ saveLike }) {
  const [liked, setLiked] = useState(false);
  const [optimistic, setOptimistic] = useOptimistic(liked);
  const [pending, startTransition] = useTransition();
  const [error, setError] = useState('');
  function like() {
    setError('');
    startTransition(async () => {
      setOptimistic(true);
      try {
        const confirmed = await saveLike();
        startTransition(() => setLiked(confirmed));
      } catch {
        setError('Could not save your like.');
      }
    });
  }
  return <><button disabled={pending || liked} onClick={like}>
    {optimistic ? 'Liked' : 'Like'}
  </button><p role="status">{error}</p></>;
}

Here saveLike returns the server-confirmed boolean. Success updates the base state; failure leaves it unchanged, so the temporary overlay disappears when the Action finishes. The nested Transition marks the post-await confirmation update correctly. Optimism does not persist data, make a request idempotent, or resolve concurrent-write conflicts; those require explicit application and server policies.

71. What is the use API, and how does it differ from ordinary Hooks?

use reads a resource during rendering, notably a Context value or the result of a cached Promise. With a pending Promise, rendering suspends; a surrounding Suspense boundary can show a fallback. A rejection is handled by an error boundary rather than by treating the rejected value as ordinary data.

JSX example

import { use } from 'react';

function Welcome({ messagePromise, visible }) {
  if (!visible) return null;
  const message = use(messagePromise);
  return <p>{message}</p>;
}
import { use } from 'react';

function Welcome({ messagePromise, visible }) {
  if (!visible) return null;
  const message = use(messagePromise);
  return <p>{message}</p>;
}
import { use } from 'react';

function Welcome({ messagePromise, visible }) {
  if (!visible) return null;
  const message = use(messagePromise);
  return <p>{message}</p>;
}

Unlike ordinary Hooks, use may be called conditionally or in loops, but only inside a component or Hook. It cannot be enclosed in try/catch. The promise must remain the same instance across relevant rerenders, typically through a framework cache, deliberate cache, or a Server Component-provided promise. Creating fetch() directly in every client render defeats that requirement.

Reading Context still uses the nearest provider, and is not supported in Server Components. React 19.3 additionally supports use(browser()) for browser-only rendering with a Suspense fallback; it is a specific API, not permission to call browser globals in arbitrary server-rendered code.

72. How does useSyncExternalStore integrate an external store safely?

useSyncExternalStore connects React to state owned outside React through a subscription and a snapshot reader. React can recheck snapshots while coordinating rendering, avoiding the inconsistencies that a hand-written subscription Effect can introduce.

Browser connectivity example with an explicit server snapshot:

JSX example

import { useSyncExternalStore } from 'react';

function subscribe(notify) {
  window.addEventListener('online', notify);
  window.addEventListener('offline', notify);
  return () => {
    window.removeEventListener('online', notify);
    window.removeEventListener('offline', notify);
  };
}
const getSnapshot = () => navigator.onLine;
const getServerSnapshot = () => true;

function ConnectionStatus() {
  const online = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
  return <p>{online ? 'Network available' : 'Offline'}</p>;
}
import { useSyncExternalStore } from 'react';

function subscribe(notify) {
  window.addEventListener('online', notify);
  window.addEventListener('offline', notify);
  return () => {
    window.removeEventListener('online', notify);
    window.removeEventListener('offline', notify);
  };
}
const getSnapshot = () => navigator.onLine;
const getServerSnapshot = () => true;

function ConnectionStatus() {
  const online = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
  return <p>{online ? 'Network available' : 'Offline'}</p>;
}
import { useSyncExternalStore } from 'react';

function subscribe(notify) {
  window.addEventListener('online', notify);
  window.addEventListener('offline', notify);
  return () => {
    window.removeEventListener('online', notify);
    window.removeEventListener('offline', notify);
  };
}
const getSnapshot = () => navigator.onLine;
const getServerSnapshot = () => true;

function ConnectionStatus() {
  const online = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
  return <p>{online ? 'Network available' : 'Offline'}</p>;
}

The server assumption also supplies the initial hydration snapshot; React then reads the browser value. Network availability is not proof that a particular API is reachable. For object-valued stores, getSnapshot must reuse the same immutable snapshot until the store changes, not allocate a fresh object on every call. Keep subscribe stable and return cleanup. With server-rendered data, serialize the same initial snapshot to the client rather than inventing mismatched defaults.

73. What causes hydration mismatches, and how do you fix them?

A hydration mismatch occurs when the client's initial React output does not agree with the server-generated HTML it is attaching to. Common causes include timestamps or random values calculated during render, browser-only branching, different locale formatting, invalid HTML nesting, and different initial data.

I would reproduce with the server response and the exact hydration inputs, then compare the first differing subtree. Fixes include transferring the server snapshot to the client, using deterministic formatting, correcting markup, and delaying genuinely browser-specific presentation until after hydration. A two-pass approach can intentionally render a shared placeholder first, then update in an Effect, but it adds a render and may introduce a visual change.

suppressHydrationWarning is a narrow escape hatch for an unavoidable one-level text or attribute difference, not a repair for an inconsistent tree. React does not guarantee that all mismatched attributes will be patched. I would also inspect extensions or scripts that modify HTML before hydration and report recoverable hydration errors through the root's supported error callbacks or the framework's equivalent integration.

74. How do server-side rendering and client rendering differ from Server Components?

Client rendering builds the interface in the browser from JavaScript. Server-side rendering produces HTML ahead of browser execution, potentially streaming it in pieces; hydration then attaches React behavior to that HTML. SSR can improve initial delivery, but interactive components still need their client code.

Server Components answer a different question: which components execute outside the client bundle. They run in a separate server or build environment and produce a representation that can be composed with Client Components. They can access server-side data directly, but cannot own browser interaction state or use browser-only Hooks. Their implementation code is not shipped as client JavaScript.

These approaches can coexist. An RSC-capable framework can render Server Components, include Client Component boundaries, server-render the resulting page, and hydrate its interactive parts. A Client Component is therefore not synonymous with a component that never runs during server HTML generation. React exposes the underlying capabilities; routing, request handling, bundling, deployment, and data-cache policies depend on the selected framework or integration, not a universal React application architecture.

75. What do use client and use server mark in an RSC-capable application?

'use client' marks a module boundary for client-executable code in an RSC-capable application. Its imported dependency subtree becomes part of the client side of that module graph. It is not a directive that disables server HTML rendering, and it does not mean every component visually nested beneath that component must be authored as a Client Component. Server-rendered content can be passed through composition slots.

'use server' marks async Server Functions that client code can invoke through the framework's server integration. It can appear at a supported function or module boundary. It does not mark a component as a Server Component; that is a common interview misconception.

Both directives must be placed as directives, before ordinary module code or at the start of the relevant function body. Values crossing boundaries must satisfy React's supported serialization rules, which are not simply โ€œanything JSON can contain.โ€ A server-callable function is a security boundary: validate inputs and authorize the operation on the server. Neither directive automatically protects data merely because the UI hides an action.

76. What is Activity used for, and what happens to hidden Effects?

Activity hides a subtree while retaining its state and DOM for later reuse. That is useful for a tab or panel whose draft, scroll position, or retained DOM should not be discarded whenever it becomes temporarily invisible.

JSX example

import { Activity } from 'react';

function PersistentPanel({ visible, children }) {
  return <Activity mode={visible ? 'visible' : 'hidden'}>
    <section>{children}</section>
  </Activity>;
}
import { Activity } from 'react';

function PersistentPanel({ visible, children }) {
  return <Activity mode={visible ? 'visible' : 'hidden'}>
    <section>{children}</section>
  </Activity>;
}
import { Activity } from 'react';

function PersistentPanel({ visible, children }) {
  return <Activity mode={visible ? 'visible' : 'hidden'}>
    <section>{children}</section>
  </Activity>;
}

When hidden, React uses display: none and cleans up the subtree's Effects. When visible again, it recreates those Effects and restores the retained state. Hidden children can still render in response to changed props at lower priority; this is not a total freeze or removal from memory.

I would make every Effect's cleanup actually release subscriptions, timers, and other resources. Activity cannot undo work that escaped the Effect lifecycle or make a hidden third-party widget behave correctly by itself. Retaining many large panels also has a memory cost. I would use ordinary conditional unmounting when state should be discarded, and explicitly test focus and resource behavior when switching visibility.

77. How do ViewTransition and Fragment refs fit into React 19.3?

Both View Transitions and Fragment refs are stable in React 19.3; describing them as Canary-only is now outdated. They solve different problems.

ViewTransition coordinates visual transitions through the browser's View Transition API when an eligible Transition or Suspense reveal changes its subtree. Urgent updates are not automatically animated.

JSX example

import { ViewTransition, startTransition, useState } from 'react';

function Expandable() {
  const [open, setOpen] = useState(false);
  return <>
    <button onClick={() => startTransition(() => setOpen(v => !v))}>
      Toggle details
    </button>
    {open && <ViewTransition><section>Details</section></ViewTransition>}
  </>;
}
import { ViewTransition, startTransition, useState } from 'react';

function Expandable() {
  const [open, setOpen] = useState(false);
  return <>
    <button onClick={() => startTransition(() => setOpen(v => !v))}>
      Toggle details
    </button>
    {open && <ViewTransition><section>Details</section></ViewTransition>}
  </>;
}
import { ViewTransition, startTransition, useState } from 'react';

function Expandable() {
  const [open, setOpen] = useState(false);
  return <>
    <button onClick={() => startTransition(() => setOpen(v => !v))}>
      Toggle details
    </button>
    {open && <ViewTransition><section>Details</section></ViewTransition>}
  </>;
}

A Fragment ref instead exposes a FragmentInstance for interacting with grouped DOM children without inserting a wrapper element. Its methods support tasks such as focus, event listeners, measurement, and observation. Use explicit <Fragment ref={ref}>, because shorthand fragments cannot carry the ref. It is not itself an HTMLElement.

I would treat animation as progressive enhancement, respect reduced-motion preferences, and verify target-browser behavior. Neither API replaces semantic HTML or turns visual grouping into an accessible widget automatically.

78. How do you test asynchronous UI updates without relying on arbitrary delays?

I would wait for an observable outcome rather than sleep for a guessed number of milliseconds. Testing Library's findBy queries wait for an element to appear; waitFor retries an assertion until it passes or times out. Await simulated user interactions as well as the UI response.

Illustrative test-body excerpt: screen, waitFor, and userEvent come from Testing Library; the test has rendered the application's search form with a controlled mock service.

JSX example

const user = userEvent.setup();
await user.type(screen.getByRole('searchbox', { name: 'People' }), 'Ada');
await user.click(screen.getByRole('button', { name: 'Search' }));
const result = await screen.findByRole('link', { name: 'Ada Lovelace' });
expect(result).toBeVisible();
await waitFor(() => {
  expect(screen.queryByText('Searchingโ€ฆ')).not.toBeInTheDocument();
});
const user = userEvent.setup();
await user.type(screen.getByRole('searchbox', { name: 'People' }), 'Ada');
await user.click(screen.getByRole('button', { name: 'Search' }));
const result = await screen.findByRole('link', { name: 'Ada Lovelace' });
expect(result).toBeVisible();
await waitFor(() => {
  expect(screen.queryByText('Searchingโ€ฆ')).not.toBeInTheDocument();
});
const user = userEvent.setup();
await user.type(screen.getByRole('searchbox', { name: 'People' }), 'Ada');
await user.click(screen.getByRole('button', { name: 'Search' }));
const result = await screen.findByRole('link', { name: 'Ada Lovelace' });
expect(result).toBeVisible();
await waitFor(() => {
  expect(screen.queryByText('Searchingโ€ฆ')).not.toBeInTheDocument();
});

The matchers require the test environment's DOM matcher setup. I would keep actions outside waitFor, since retries could repeat them. For races, deferred mock promises let the test resolve requests in the wrong order deliberately. Fake timers suit actual debounce behavior, not arbitrary rendering delays. Tests should assert pending, success, failure, and stale-response behavior rather than implementation-specific render counts.

79. How do you diagnose a slow list before adding memoization or virtualization?

I would reproduce the slow interaction with realistic data and separate React rendering time from network, JavaScript, layout, and paint costs. A list that feels slow may be waiting on images or performing costly browser layout even when its React components render quickly.

Profiler instrumentation excerpt; Rows and items are application-provided:

JSX example

import { Profiler } from 'react';

function ProfiledRows({ Rows, items }) {
  return <Profiler id="results" onRender={(id, phase, actualDuration) => {
    console.log({ id, phase, actualDuration });
  }}>
    <Rows items={items} />
  </Profiler>;
}
import { Profiler } from 'react';

function ProfiledRows({ Rows, items }) {
  return <Profiler id="results" onRender={(id, phase, actualDuration) => {
    console.log({ id, phase, actualDuration });
  }}>
    <Rows items={items} />
  </Profiler>;
}
import { Profiler } from 'react';

function ProfiledRows({ Rows, items }) {
  return <Profiler id="results" onRender={(id, phase, actualDuration) => {
    console.log({ id, phase, actualDuration });
  }}>
    <Rows items={items} />
  </Profiler>;
}

actualDuration measures render work for that committed update, not total user-perceived latency. I would inspect why rows render, expensive filtering or sorting, unstable keys, broad Context updates, and avoidable state placed above the list. Then I would choose a targeted fix: memoization for repeated unchanged work, better algorithms for computation, or virtualization for excessive mounted DOM. Virtualization introduces focus, accessibility, and measurement trade-offs. Finally, I would compare the same interaction before and after with representative production-like conditions, not infer a win from fewer console logs.

80. How would you implement a resilient search interface with keyboard access?

I would separate the editable query, the submitted or requested query, and the results associated with that request. The data layer should cancel or ignore obsolete responses and expose loading, empty, error, and success states. If previous results remain visible, the interface should make their staleness clear. Debouncing requests and deferring expensive rendering solve different problems.

For ordinary search, native form controls provide a reliable keyboard baseline:

JSX example

function SearchBox({ query, setQuery, onSubmit, statusMessage }) {
  return <form role="search" onSubmit={event => {
    event.preventDefault();
    onSubmit(query);
  }}>
    <label>Search people <input type="search" value={query}
      onChange={event => setQuery(event.target.value)} /></label>
    <button type="submit">Search</button>
    <p role="status">{statusMessage}</p>
  </form>;
}
function SearchBox({ query, setQuery, onSubmit, statusMessage }) {
  return <form role="search" onSubmit={event => {
    event.preventDefault();
    onSubmit(query);
  }}>
    <label>Search people <input type="search" value={query}
      onChange={event => setQuery(event.target.value)} /></label>
    <button type="submit">Search</button>
    <p role="status">{statusMessage}</p>
  </form>;
}
function SearchBox({ query, setQuery, onSubmit, statusMessage }) {
  return <form role="search" onSubmit={event => {
    event.preventDefault();
    onSubmit(query);
  }}>
    <label>Search people <input type="search" value={query}
      onChange={event => setQuery(event.target.value)} /></label>
    <button type="submit">Search</button>
    <p role="status">{statusMessage}</p>
  </form>;
}

Here the application-provided onSubmit owns request and error handling. Results should use actual links or buttons with visible focus. If the design requires an autocomplete popup, I would implement the full combobox pattern: expanded state, active-option relationships, arrow-key movement, Enter selection, and Escape dismissal, while preserving normal text-editing keys. I would test rapid typing, reordered responses, keyboard-only use, and assistive-technology announcements.

React Interview Questions and Answers for Experienced

The experienced answers address large-application design, migration, performance diagnosis, server/client boundaries, resilience, and team practices. The best solution depends on measured bottlenecks and explicit product guarantees rather than adopting every available React feature.

81. How would you choose React application boundaries and a rendering architecture?

I would choose boundaries around user journeys, data ownership, and independent failure recovery before choosing a rendering technique. A public product page and an authenticated editing workspace may share components but have different freshness, interactivity, and deployment needs.

Static generation suits content whose freshness can tolerate a rebuild. Request-time rendering can improve the initial delivery of personalized content, while client rendering can suit a highly interactive workspace. Server Components are another dimension: they decide where component code executes, rather than simply whether HTML is initially produced on a server. An RSC-capable framework supplies the necessary bundling and transport integration; React alone does not provide a complete hosting architecture.

I would prototype a representative route, measuring initial content, interaction readiness, JavaScript cost, server latency, and operational complexity. Then I would document cache ownership, authentication boundaries, loading states, and recovery paths. I would prefer one coherent application with domain modules unless separate deployments solve a demonstrated organizational problem. The architecture should remain understandable during an incident, not merely score well in a demo.

82. How would you choose between local state, Context, an external store, and a server-state cache?

I would classify each value by ownership, lifetime, update frequency, and who needs to observe it. A menu's open state belongs near the menu. A multi-step editor may justify a reducer at the feature boundary. Context is useful for distributing values such as locale or an existing state owner through a subtree; it does not automatically provide selective subscriptions or a server cache.

An external store becomes useful when independently mounted consumers need coordinated client state, fine-grained subscriptions, or integration with a non-React system. I would inspect its React subscription contract rather than choosing it solely because an application is large. Server-owned records need freshness, request deduplication, invalidation, retry, and mutation reconciliation, usually managed by a framework or query cache.

I would avoid copying the same record into Context, a store, and component state. A local editing draft is an intentional exception with explicit save and reset rules. Tests should cover navigation, logout, tenant changes, and concurrent edits so that persistence and reset behavior match the chosen owner.

83. How would you migrate a large class-component application incrementally?

I would migrate around business changes and verified behavior, not rewrite every class because function components are preferred. Class and function components can coexist, so the first step is to map critical workflows, legacy lifecycle usage, subscription ownership, and available regression coverage.

For each selected component, I would preserve its public props and user-visible behavior, then separate rendering, event-driven work, and external synchronization. Lifecycle methods should not be translated mechanically into one giant Effect. A subscription's setup and cleanup belong together; derived display values often need no Effect. I would check the difference between class state merging and Hook state replacement, and review callbacks that previously read mutable instance fields for stale-closure risks.

Error boundaries can remain class-based or use an established boundary abstraction; replacing them with an ordinary function does not reproduce their behavior. I would migrate a bounded feature, run interaction and accessibility tests, and compare production errors before expanding. The success criteria are simpler ownership and unchanged workflows, not a particular percentage of function components.

84. How would you plan a React version upgrade without breaking production workflows?

I would inventory React, its renderer, the framework, compiler integration, testing tools, and packages with React peer dependencies. The upgrade target should be a verified supported release, with security fixes and framework compatibility considered together. As of this article's October 2026 review, React's stable documentation targets 19.3.

I would read the release notes and relevant migration guides, apply codemods on a reviewable branch, and distinguish mechanical changes from behavior changes. For a React 18 application moving to 19, the official guide recommends 18.3 first to expose useful deprecation warnings. I would not assume that guidance replaces the framework's own upgrade sequence.

Validation should cover production builds, hydration, forms, error reporting, refs, third-party widgets, and critical user journeys, not only unit tests. I would avoid combining the upgrade with a router rewrite or broad redesign. A staged rollout would compare errors, interaction latency, and business completion rates against a baseline, with an artifact-level rollback available. Any data or protocol migration must remain compatible with that rollback.

85. How would you introduce React Compiler into a mature codebase?

I would treat React Compiler adoption as a measurable build change, starting with a well-tested feature rather than enabling it everywhere immediately. First I would establish a production performance baseline and enable the recommended lint diagnostics, fixing violations of React's purity and Hook rules instead of relying on accidental behavior.

The compiler supports incremental adoption through directory configuration, opt-in directives, and runtime gating. I would choose the approach that fits our build system and lets us compare compiled and uncompiled behavior. Existing manual memoization would not be deleted wholesale in the same change; removing it should be a separate, justified cleanup after compatibility and performance are understood.

I would test identity-sensitive integrations, custom Hooks, third-party widgets, and representative interactions. Temporary exclusions should have an owner and a reproducible reason, not become a blanket escape hatch. Rollout evidence would include render cost, interaction latency, build time, and error rates. The compiler can reduce avoidable React work, but it does not repair network waterfalls, oversized assets, or an application with incorrectly modeled state.

86. How would you diagnose an interaction that is slow even though React renders quickly?

I would measure the entire interval from input to visible feedback. A short React render does not rule out input delay from another long task, expensive event-handler code, synchronous storage access, style recalculation, layout, painting, or a slow network dependency.

I would reproduce the interaction with representative data and device constraints, then correlate the browser performance trace with React Performance tracks. The timeline should show whether time is spent before React starts, in rendering or commit work, in layout Effects, or after the DOM changes. I would also inspect repeated layout reads and writes and unrelated third-party scripts occupying the main thread.

The fix follows the evidence. I might reduce DOM complexity, batch measurements, move a pure CPU-heavy calculation to a worker, or show immediate feedback while data loads. Wrapping an expensive synchronous function in a transition does not move it to another thread. I would rerun the same scenario and compare user-visible latency distributions, checking that the improvement did not increase network traffic, memory usage, or error frequency.

87. How would you interpret Profiler measurements without optimizing the wrong bottleneck?

I would begin with a user-visible slow interaction and use the Profiler to identify contributing React work. actualDuration measures rendering for a committed update within the profiled subtree. baseDuration estimates the cost of rendering that subtree without memoization from recent component measurements; it is not a guaranteed baseline or total interaction time.

I would compare repeated runs using the same data, device conditions, and build configuration. Development checks and profiling overhead can change timings, so a large development number alone is not proof of a production regression. Nested profiler durations overlap; summing them would double-count work.

If a subtree renders often but cheaply, optimizing it may deliver less value than fixing one expensive render or a slow layout Effect elsewhere. I would inspect update causes, changed props, Context churn, and state placement before adding memoization. Finally, I would correlate React measurements with the browser trace and end-to-end interaction latency. An optimization is successful only if it improves the target workflow without making state synchronization or maintenance more fragile.

88. How would you design Suspense boundaries to avoid unnecessary loading waterfalls?

I would align Suspense boundaries with meaningful reveal units: content users need together should appear together, while an independent recommendations panel should not hold back the main result. Boundaries describe loading presentation; they do not automatically start requests early or parallelize a serial data dependency.

I would draw the request graph and distinguish genuine dependencies from accidental ones. Independent route data and code can often be started by the router, framework loader, or an explicit preload before a child renders. If every child starts fetching only after its parent finishes loading, adding more spinners merely exposes the waterfall. Ordinary Effect-based requests do not activate Suspense.

I would use the framework's supported Suspense data integration or stable cached Promises rather than inventing an uncached Promise during every render. Fallbacks should preserve layout and describe what is loading. Already useful content can remain visible during a transition, with a clear pending indication. Tests would delay each dependency independently, verify reveal order and focus continuity, and distinguish loading recovery from errors handled by an error boundary.

89. How would you design streaming SSR with meaningful loading and error recovery?

I would design a small, useful shell that can be delivered before slower content, then place Suspense boundaries around independently recoverable sections. Navigation and context should remain understandable while a slow panel streams. Streaming SSR produces HTML progressively; it does not by itself mean the application uses Server Components.

The server integration needs explicit shell-ready, shell-error, and error-reporting behavior. With Node's renderToPipeableStream, an error before the shell is available can select an alternate response. After headers and bytes have been sent, the server cannot retroactively replace the HTTP status. Errors inside a Suspense region can leave a fallback and allow client rendering to retry; a client error boundary is still needed if that retry fails.

I would establish request deadlines, abort abandoned rendering, and verify that the proxy or hosting layer actually forwards chunks instead of buffering them. Tests should include slow data, a failing shell, a failing streamed section, missing JavaScript, and disconnected clients. I would measure both time to useful content and time to interaction, plus server resource consumption under load.

90. How would you investigate an intermittent hydration failure in production?

I would first capture a privacy-conscious incident record: release identifiers, route, component stack, locale, relevant feature-flag decisions, and the recoverable hydration error. hydrateRoot exposes onRecoverableError; a framework may provide its own integration for configuring that reporting.

Then I would compare the server's initial inputs and HTML with the client's first render, before Effects legitimately change anything. Typical hypotheses include timestamps, random values, locale or timezone differences, invalid HTML nesting, browser-only branches, inconsistent external-store snapshots, and mismatched application assets. Extensions or intermediary HTML rewriting are separate possibilities, not reasons to dismiss a reproducible application bug.

I would replay the failing inputs against the exact release, vary hydration timing, and test cached pages across deployments. The fix should make the first render deterministic or explicitly defer genuinely client-only content without changing the initial markup. suppressHydrationWarning is a narrow escape hatch, not a repair strategy, and React does not promise to patch every mismatch. I would track the error rate after rollout and verify that the affected interaction still works, not merely that the warning disappeared.

91. How would you design Server and Client Component boundaries to reduce client JavaScript?

In an RSC-capable framework, I would keep data access and noninteractive presentation on the server where practical, then introduce Client Components around interactions that require state, event handlers, or browser APIs. A product description can remain server-rendered while a quantity selector is interactive.

The important boundary is the module dependency graph. A 'use client' module brings its transitive imports into client execution, so placing the directive at a broad layout can pull substantial code into the browser. Instead, a Server Component can pass rendered content as children to a small client wrapper without making the source component a client import.

I would pass only the data needed by the client using supported serializable values. Keeping a database call on the server does not prevent information from leaking if its result is unnecessarily serialized into props. Client Components may also participate in initial server HTML rendering; the name does not mean browser-only rendering in every framework. I would inspect the actual client bundle and payload, test hydration and navigation, and measure whether additional server round trips offset the JavaScript savings.

92. How would you secure Server Functions that accept untrusted input?

I would review each Server Function as a remotely callable mutation endpoint. The 'use server' directive establishes server execution and framework transport; it does not authenticate the caller or authorize access to a record.

Every invocation should derive identity from a trusted server session, validate the input's shape and limits, and authorize the specific operation on the specific tenant-scoped resource. An identifier supplied through a hidden input or a bound argument is still client-controlled input for security purposes. I would enforce business invariants in the server transaction, use parameterized database access, and return only the fields the caller needs.

The deployment review should verify the framework's origin and CSRF protections, supported patch level, request-size limits, and abuse controls rather than assume React provides them all. Expected validation failures should become safe user-facing results; unexpected failures need redacted telemetry. I would test unauthenticated calls, cross-tenant identifiers, revoked permissions, replayed submissions, and malformed payloads directly at the server boundary. Hiding the submit button is a usability measure, never the authorization check.

93. How would you prevent one user's data from leaking through server caches?

I would inventory every cache separately: React's Server Component memoization, a framework data cache, an application singleton, a CDN, and the browser's query cache can have very different lifetimes and sharing rules. React's cache is invalidated for each server request; that guarantee does not extend to a framework's persistent cache or an arbitrary module-level Map.

For data shared across requests, I would define an explicit ownership policy and include all relevant tenant, identity, permission, locale, and representation dimensions in the cache design, or avoid sharing that response. Cache hits must not bypass the required authorization decision. Revoked permissions and changed membership need a freshness or invalidation strategy; a sufficiently elaborate key alone is not an authorization system.

On the client, logout and tenant switching should clear or isolate the appropriate cached records and drafts. I would inspect response caching headers and framework defaults rather than assume personalized HTML is private. Tests would interleave two users and two tenants, warm caches in different orders, change permissions, and verify both returned data and serialized page payloads.

94. How would you decide whether a value belongs in the URL, client state, or persistent storage?

I would put navigation state in the URL when users should be able to bookmark, share, refresh, or revisit it with Back and Forward. Search terms, sorting, pagination, and selected record identifiers often qualify. Temporary input composition, hover state, and an unsaved dialog usually belong in client state. Durable drafts or preferences may need persistent storage with an explicit scope and lifetime.

URL values need parsing and validation. This standalone helper normalizes a page number; the router would own writing it back:

JavaScript example

function readPage(search) {
  const raw = new URLSearchParams(search).get('page');
  const page = Number(raw);
  return Number.isSafeInteger(page) && page > 0 ? page : 1;
}
function readPage(search) {
  const raw = new URLSearchParams(search).get('page');
  const page = Number(raw);
  return Number.isSafeInteger(page) && page > 0 ? page : 1;
}
function readPage(search) {
  const raw = new URLSearchParams(search).get('page');
  const page = Number(raw);
  return Number.isSafeInteger(page) && page > 0 ? page : 1;
}

I would avoid two competing sources of truth that synchronize each other through Effects. An input can keep a temporary draft and commit it to the URL deliberately. Sensitive values should not casually enter URLs or browser storage. Persistent data needs schema migration, failure handling, and account isolation. Tests would cover malformed URLs, navigation history, refresh, expired records, and a different user signing in on the same browser.

95. How would you handle optimistic mutations when updates fail or arrive out of order?

I would separate confirmed server state from pending user operations. Each operation needs a stable client identifier and enough intent to reapply it, such as setting a quantity to a value rather than blindly restoring an old screen snapshot. useOptimistic can project temporary state while an Action is pending, but it does not supply server conflict resolution.

If mutation A fails after mutation B succeeds, restoring the entire pre-A snapshot can erase B. Instead, I would remove or mark the failed operation and recompute the optimistic view over the newest confirmed state. Responses should be associated with their operation and server version so an older result cannot silently overwrite a newer confirmation. Related mutations may need serialization or a documented conflict policy.

The UI should distinguish pending, failed, and confirmed states, preserve recoverable input, and offer an intentional retry. The server still needs authorization and duplicate-operation protection. Tests would control completion order, reject individual operations, simulate an ambiguous timeout, and verify that a background refetch neither duplicates optimistic items nor loses successful edits.

96. How would you prevent duplicate submissions and stale writes in a React application?

I would address duplicate intent and stale state at different layers. Disabling a submit button while pending improves the experience, but it cannot prevent retries, multiple tabs, or a request repeated after an uncertain timeout. A server-recognized operation identifier can deduplicate a logical submission when its scope, payload binding, and retention policy are defined.

Stale writes need a separate concurrency contract. For an edit based on version 12, the server should atomically verify that version 12 is still current before applying it, using a version field or an HTTP conditional request such as If-Match. A conflict should preserve the user's draft and offer refresh, merge, or retry rather than silently overwrite another edit.

React Action state can express pending and validation outcomes, but it is not a distributed lock. I would reconcile ambiguous outcomes before generating a new operation identifier. Tests would include rapid clicks, keyboard submission, two tabs, lost responses after commit, and concurrent edits from another user. Telemetry should distinguish duplicate retries, conflicts, and genuine server failures.

97. How would you build a real-time dashboard without overwhelming rendering?

I would separate the incoming event rate from the display update rate. A dashboard might ingest hundreds of measurements per second while presenting a useful visual snapshot much less frequently. The data layer should validate and sequence events, maintain bounded buffers, and define whether intermediate samples may be coalesced or must be preserved for later analysis.

React should subscribe to the smallest useful immutable snapshot, using a store with a correct React integration such as useSyncExternalStore. Publishing a new whole-dashboard object for every packet would force avoidable work. I would aggregate by entity, update visible panels selectively, and avoid sending large historical arrays through Context. CPU-heavy parsing or aggregation may justify a worker, while the main thread retains UI ownership.

Connection status, data age, and gaps should be visible; a fast chart displaying stale values is still wrong. I would test bursts, reconnect replay, duplicate events, sequence gaps, and background tabs. Measurements would include input responsiveness, dropped or coalesced events, memory growth, and time from a received event to the displayed snapshot.

98. How would you manage external subscriptions under concurrent rendering and StrictMode?

I would keep rendering free of subscription side effects. For a React component reading an external store, useSyncExternalStore provides the subscription and snapshot protocol; the snapshot must remain referentially stable while the relevant data is unchanged. Mutable stores need cached immutable snapshots, not a fresh object on every read.

The subscription function must return cleanup and should have stable identity unless a real subscription dependency changes. For server rendering, the server snapshot must agree with the client's hydration snapshot. React can restart a transition as blocking if the external store changes before commit, so I would not assume wrapping a store mutation in startTransition makes that mutation nonblocking.

Other external systems belong in Effects with symmetric setup and teardown. StrictMode's development checks should reveal duplicate connections or missing cleanup rather than be silenced with a run-once ref. I would test mount, cleanup, remount, dependency changes, and events arriving during teardown. The invariant is one intended live subscription per owner, with no updates from a connection that is no longer current.

99. How would you design a virtualized, accessible data grid?

I would first determine whether the product needs an interactive grid or simply a table. A read-only table with pagination can be easier to use and maintain. A true grid introduces a keyboard navigation model, focus management, editing behavior, and selection semantics in addition to virtualization.

The logical row and cell identities must be independent of recycled DOM nodes. Focus should never disappear because the virtualizer unmounted the active cell; I would keep it rendered or move focus deliberately before recycling it. The implementation needs consistent row and column counts and indices when only part of the dataset exists in the DOM. Sorting, filtering, and selection should operate on stable record identifiers rather than visible positions.

I would specify arrow-key navigation, entry into cell editing, escape from editing, and a predictable tab stop. Overscan and measurement choices should be tested with variable-height content, zoom, and rapid scrolling. Automated checks are useful, but keyboard and screen-reader testing are essential. I would also provide a practical route to accessing or exporting data beyond the current viewport.

100. How would you improve a large form without making validation or accessibility fragile?

I would profile typing, validation, and submission separately before replacing the form architecture. If every keystroke rerenders unrelated panels, I would localize field state or use selective subscriptions rather than immediately adding memoization everywhere. Controlled and uncontrolled fields can both work, but each field needs a consistent ownership model throughout its lifetime.

Validation should distinguish immediate formatting assistance, cross-field rules, expensive asynchronous checks, and authoritative server validation. A late uniqueness-check response must not overwrite the result for a newer value. I would avoid announcing every keystroke as an error and preserve input after a failed submission.

Each control needs a stable label, useful instructions, and programmatically associated error text. On submission failure, an error summary or carefully chosen focus target should help users find the problem without losing their place. Native form semantics, keyboard submission, and appropriate autocomplete remain valuable even with a form library. Tests would include composition input, pasted values, slow validation, server rejection, disabled controls, and conditional fields being removed and restored. Success means faster interaction without weakening validation or accessibility.

101. How would you build a reusable accessible dialog with portals and focus management?

I would start with a tested accessible dialog primitive or the native dialog element where it meets the product's requirements. If implementing the behavior ourselves, I would define opening, closing, focus restoration, nested dialogs, and dismissal policy before styling it.

A portal can escape clipping and stacking constraints, but it only changes DOM placement. Context and React event propagation still follow the React tree, so clicking inside the dialog can reach an ancestor handler unless the design accounts for it. A portal does not supply modal behavior automatically.

The dialog needs an accessible name, suitable initial focus, a contained tab sequence, an Escape path, and background content that is actually inert while modal. On close, focus normally returns to the trigger or another logical target if that trigger no longer exists. Destructive workflows may require more careful initial focus and explicit confirmation. I would test keyboard-only use, screen readers, long content, zoom, nested overlays, and route changes while open. Cleanup must restore scroll and background interaction even when the dialog unmounts unexpectedly.

102. How would you choose error-boundary placement and production error telemetry?

I would place error boundaries where users can understand and recover from a partial failure: around a route's main content, an independent dashboard panel, or a risky embedded integration. A boundary around every small component creates noisy, fragmented recovery; one boundary around the entire application can discard too much useful UI.

The fallback should explain what failed, preserve unaffected navigation, and offer a meaningful recovery action. Resetting a boundary without changing the failing condition can create a retry loop. Expected validation failures usually belong in ordinary UI state rather than an exception fallback.

Telemetry should include the release, route, component stack, error category, and a safe correlation identifier, with deduplication and sensitive-data filtering. Root error callbacks and boundary reporting should not double-count the same incident. Boundaries catch descendant rendering failures, but not arbitrary event-handler errors, timer callbacks, server rendering failures, or their own errors; errors in Actions started through useTransition are a relevant exception. I would test each failure path separately and confirm both that recovery works and that the production report contains enough evidence to diagnose it.

103. How would you create a component testing strategy across unit, integration, and end-to-end tests?

I would organize tests by risk and feedback speed. Pure reducers, parsers, and domain rules deserve focused unit tests, including edge cases that are awkward to reach through a browser. Component integration tests should render real collaborating components and assert behavior through accessible roles, names, and user interactions rather than private state or exact component structure.

Network boundaries can be controlled so tests exercise loading, empty results, errors, retries, and delayed responses deterministically. I would avoid mocking every child and Hook, because that often proves only that the mocks agree. React updates must be flushed before assertions using testing utilities that integrate with async act, or act directly where appropriate.

A smaller end-to-end suite should cover critical journeys in a real browser, including routing, authentication integration, hydration, focus, and deployment assets. Accessibility automation complements manual keyboard and assistive-technology checks. I would review flaky tests as diagnostic evidence, keep fixtures representative, and make failures attributable. Coverage numbers are useful signals, but the goal is confidence that important workflows survive realistic changes and failures.

104. How would you test a Suspense, transition, or optimistic-update workflow?

I would control the asynchronous boundary instead of inserting arbitrary sleeps. A deferred Promise or controllable network handler lets the test observe the initial state, trigger the interaction, assert the pending UI, and then resolve or reject the exact operation being tested.

For Suspense, I would verify the intended fallback and reveal order, plus whether already useful content remains available during a transition. For an optimistic mutation, I would assert the temporary result before the server responds, then verify confirmation or rollback with the original input preserved when recovery is possible. Two controlled requests should resolve in reverse order to expose stale-response bugs.

Assertions should describe what users can see and operate, not an exact render count or internal scheduling order. Awaiting React-aware test utilities is important, but act does not automatically finish an unrelated unresolved network request. I would include rejection, cancellation, unmount, retry, and navigation cases. Finally, browser tests should cover focus continuity and real loading behavior, since a simulated DOM cannot establish actual painting, layout, or perceptual responsiveness.

105. How would you design a typed component API for a shared design system?

I would model meaningful product choices rather than expose every internal styling decision as a prop. The API should make common use straightforward and contradictory combinations difficult. For a toggle, controlled and uncontrolled ownership can be represented explicitly. This TypeScript type is an API excerpt, not a component implementation:

TypeScript example

type ToggleProps = { label: string } & (
  | { checked: boolean; onCheckedChange: (value: boolean) => void;
      defaultChecked?: never }
  | { checked?: never; defaultChecked?: boolean;
      onCheckedChange?: (value: boolean) => void }
);
type ToggleProps = { label: string } & (
  | { checked: boolean; onCheckedChange: (value: boolean) => void;
      defaultChecked?: never }
  | { checked?: never; defaultChecked?: boolean;
      onCheckedChange?: (value: boolean) => void }
);
type ToggleProps = { label: string } & (
  | { checked: boolean; onCheckedChange: (value: boolean) => void;
      defaultChecked?: never }
  | { checked?: never; defaultChecked?: boolean;
      onCheckedChange?: (value: boolean) => void }
);

I would document default behavior, keyboard semantics, accessible naming, event payloads, and whether the component exposes a DOM ref or a narrower imperative handle. Composition is usually clearer than many interacting boolean props, while unrestricted polymorphism can make typing and semantics harder to guarantee.

Types improve authoring but do not validate runtime network data or prove accessibility. I would add type-level acceptance and rejection cases, behavioral tests, visual examples, and keyboard tests. Breaking changes need migration guidance and a deprecation path. Internal optimizations should not silently change the public ownership or event contract.

106. How would you introduce a third-party imperative widget safely into React?

I would build a narrow adapter that gives the imperative widget exclusive ownership of a dedicated DOM container. React should not simultaneously reconcile the same child nodes that the widget mutates. The adapter would expose application concepts such as selection or value rather than leak the widget's entire mutable instance.

Initialization and destruction belong in an Effect, with a ref holding the container and instance. A layout Effect is justified only when a synchronous measurement or visual correction must happen before paint. Prop changes should update the existing instance when supported; repeatedly recreating an editor can lose selection, undo history, and user input.

I would prevent feedback loops between widget events and React updates, detach listeners during cleanup, and ignore late asynchronous initialization after the owner is gone. StrictMode should be able to exercise setup and teardown without leaving duplicate widgets. Tests would include rapid mount and unmount, dependency changes, resizing, external value updates, and failures loading widget code. Browser coverage is essential for focus and geometry, and the bundle, accessibility, and security costs must be reviewed before adoption.

107. How would you support offline edits and reconnect reconciliation?

I would model offline work as a durable queue of user operations, not merely keep the latest screen state in memory. Each operation needs an identifier, owner, target record, base version, and enough payload to replay or review it. IndexedDB can store structured local data asynchronously, but persistence failures and storage eviction still need product-level handling.

The UI should distinguish local, queued, syncing, conflicted, and confirmed changes. Reconnection is a synchronization process: authenticate again, fetch authoritative changes, replay eligible operations with server deduplication, and reconcile conflicts using explicit domain rules. Last-write-wins may be acceptable for a preference but dangerous for collaborative edits or inventory changes.

A browser online event is only a hint; actual requests determine whether the service is reachable. I would preserve drafts when credentials expire and ensure one account cannot inherit another account's queue. Tests would simulate a crash after local persistence, a lost response after server commit, duplicate replay, changed permissions, and schema migration. React renders the queue's state; it does not supply an offline synchronization protocol or conflict policy.

108. How would you design multi-tenant theming and localization without unnecessary rerenders?

I would separate visual tokens, translation resources, formatting rules, and tenant identity instead of combining them into one frequently changing Context object. Many colors and spacing choices can be applied with CSS custom properties, while components that need locale or direction can subscribe to a focused provider.

Context consumers rerender when their provider value changes by Object.is; wrapping a consumer in memo does not stop a real Context update. I would keep provider values stable when their meaning is unchanged, split independent concerns, and measure before introducing more elaborate subscription machinery. A locale change legitimately needs to update translated content.

For server-rendered pages, the initial tenant, locale, timezone policy, and direction should agree with hydration inputs. Translation keys and formatting must handle plurals, interpolation, missing messages, and right-to-left layouts. Tenant switching also needs cache and draft isolation; changing the theme is not authorization. Tests would cover long translations, mixed-direction content, dynamic locale changes, and rapid tenant switches, while profiling confirms that unrelated high-frequency data updates do not invalidate the entire application shell.

109. How would you investigate a memory leak in a long-lived React page?

I would reproduce a repeatable lifecycle, such as opening and closing the same workspace, and observe memory after several cycles and garbage collection. A temporary allocation spike is not automatically a leak; the key question is which objects remain reachable when the feature should have released them.

Heap snapshots and allocation recordings can reveal retained DOM nodes, listener closures, large caches, timers, unresolved work, or third-party widget instances. I would inspect retaining paths back to a live owner rather than assume React itself is retaining every detached node. Console inspection and debugging references can also affect the experiment.

The fix should release the resource at its actual ownership boundary: remove listeners, stop timers, disconnect subscriptions, revoke object URLs, destroy widgets, or bound a cache. Activity may intentionally preserve hidden state and DOM, so retained content must be compared with the intended retention policy. I would repeat the original scenario after the fix, verify a stable memory plateau, and test long-running sessions. Adding memoization without understanding the retaining path can make memory retention worse.

110. How would you use transitions without hiding correctness or race-condition problems?

I would use transitions for updates whose rendering can yield to more urgent interaction, such as switching a complex results view. Controlled input updates should remain immediate; an expensive dependent view can update separately. Concurrent rendering means React can interrupt and restart render work, not that application JavaScript automatically runs on parallel threads.

A transition does not cancel requests, order responses, enforce authorization, or make an expensive synchronous event-handler calculation interruptible. I would establish request identity and server version rules separately. If two async transitions finish out of order, their business outcomes still need reconciliation; an appropriate Action abstraction can help, but its ordering guarantees must be understood.

In the documented API, state setters after an await still need an additional transition wrapper when they should remain transition updates. I would keep pending feedback visible and preserve useful content without disguising stale data as current. Tests would type or navigate rapidly, reverse response order, inject failures, and verify both the final state and intermediate usability. Correctness must hold even if React schedules work differently from the happy-path demo.

111. How would you adopt Activity while protecting background-resource usage?

I would use Activity for a bounded set of screens that are likely to be revisited and benefit from preserved state. It is useful for retaining an editor draft or tab state, but it should not become an unlimited cache of every page a user has opened.

When hidden, Activity preserves its children and state, hides their DOM, and cleans up their Effects. Hidden content can still rerender at lower priority when props change, and Effects are recreated when it becomes visible. That means subscriptions and timers owned correctly by those Effects should stop, but a shared connection owned above the boundary will not automatically stop. DOM-backed activity, such as media playback, may need explicit pause behavior through cleanup.

I would define retention limits and distinguish work needed by the whole application from work needed only by the visible screen. Tests would hide and reveal repeatedly, verify subscription counts, inspect network traffic, and confirm focus and draft restoration. Memory measurements should include retained DOM and large editor data. If preservation is unnecessary, normal unmounting is often the simpler resource policy.

112. How would you add View Transitions while respecting accessibility and browser support?

I would add View Transitions as progressive visual enhancement around a small interaction whose continuity genuinely benefits users. React's ViewTransition became stable in 19.3 and currently targets the DOM. It coordinates animations for qualifying transition updates and Suspense reveals; ordinary urgent updates do not automatically animate.

I would let React coordinate the browser View Transition API rather than call document.startViewTransition around competing updates. Shared-element names must be unique among simultaneously mounted boundaries, and broad wrappers should not accidentally animate unrelated content. The underlying navigation and state update must still work when the browser cannot provide the animation.

Reduced-motion support is an explicit responsibility: React does not automatically disable these animations for that preference. I would provide CSS that removes or substantially reduces motion, maintain keyboard focus and route announcements, and never delay essential feedback until an animation completes. Tests would cover unsupported environments, reduced motion, rapid repeated navigation, interrupted updates, and slow assets. I would measure rendering and animation costs on representative devices and remove animations that make the interface slower or harder to follow.

113. How would you reduce JavaScript bundle cost and verify the result?

I would inspect a production bundle by route and dependency rather than infer cost from source-file size. The relevant budget includes transferred bytes, parse and execution time, hydration work, and code needed for the first meaningful interaction. A small compressed bundle can still contain expensive startup logic.

I would remove unused dependencies, check accidental duplicate packages, and isolate large optional features behind deliberate loading boundaries. lazy with a dynamic import can defer a component's code, but the declaration should remain outside rendering and the user needs a sensible loading and error experience. Excessively granular splitting can replace byte savings with request overhead and waterfalls.

In an RSC-capable architecture, keeping suitable implementation code on the server may reduce client JavaScript, but I would verify the actual import graph and serialized payload. I would compare before and after builds under cold-cache and repeat-navigation conditions, including slower devices. Deployment tests should cover clients requesting old chunks after a release. The result should improve the target journey, not merely move the same cost to a later moment when the user clicks.

114. How would you prevent XSS and unsafe URL handling in a React frontend?

I would render untrusted text as ordinary React text wherever possible. That avoids treating the value as HTML, but it is not a universal security guarantee for every browser API. Rich HTML, URL destinations, script data, and imperative third-party widgets have different trust boundaries.

If rich HTML is required, I would use a maintained sanitizer with an explicit allowed-content policy before passing it to dangerouslySetInnerHTML. I would avoid concatenating untrusted strings into scripts, event-handler source, or DOM HTML sinks. For links and redirects, I would parse the URL and enforce the allowed protocols and, where required, allowed origins or paths. URL encoding alone does not establish that a destination is safe.

I would review server-side serialization and framework defaults, keep relevant dependencies patched, and use CSP or Trusted Types as defense in depth where supported, not substitutes for safe handling. Tests should exercise each actual rendering sink with representative malicious inputs and verify the resulting DOM or navigation. The review must include browser-side integrations that bypass React, because React's normal text rendering cannot protect those operations.

115. How would you use feature flags and staged rollouts without creating inconsistent UI state?

I would make feature assignment a stable input to a workflow, with an explicit policy for when it can change. A user should not enter a new checkout, receive a flag refresh, and unexpectedly finish in an incompatible old checkout. For server-rendered pages, the client should start with the same assignment used for the server output.

The flag boundary should own a compatible state model or deliberately migrate it. Switching component types or keys can reset React state, so draft preservation and reset behavior must be designed rather than discovered during rollout. Flags that affect authorization or server behavior need corresponding server enforcement; hiding a feature in the client is insufficient.

I would record assignment and release identifiers in privacy-conscious telemetry, compare operational and product metrics, and establish rollback criteria before expanding exposure. Tests should cover both variants, flag-service failure, mid-session updates, and old clients interacting with new server behavior. Every temporary flag needs an owner and removal condition. A staged rollout is only safe when rollback leaves user data and in-flight operations in a supported state.

116. How would you evaluate micro-frontends for a React organization?

I would evaluate micro-frontends against a concrete organizational constraint, such as independently deployed product domains with distinct owners. A modular application is usually cheaper when teams can coordinate releases. Splitting the frontend adds contracts for navigation, identity, design consistency, observability, dependencies, and failure recovery.

I would distinguish build-time package composition, runtime module loading, separate React roots, and iframe isolation; they offer different coupling and deployment tradeoffs. Separate roots do not automatically share Context or state, and each mounted root needs a clear owner and unmount lifecycle. Runtime dependency sharing can reduce duplication but introduces version compatibility risks.

The boundaries should exchange small, versioned domain contracts rather than reach into each other's stores or DOM. Authentication remains a trusted server concern even if multiple teams own the screens. I would pilot one independently valuable domain and measure deployment autonomy, integration incidents, bundle duplication, and user experience. Tests would simulate an unavailable remote module, incompatible contract versions, partial navigation failure, and cleanup after removal. I would proceed only if the organizational gains outweigh the enduring operational and UX complexity.

117. How would you share code between React web and React Native without conflating platforms?

I would share domain models, validation, reducers, API clients, and platform-independent Hooks where their dependencies allow it. I would not assume that JSX makes web and native presentation interchangeable: React DOM renders browser elements, while React Native uses platform components and platform-specific interaction and accessibility behavior.

Shared packages should depend on explicit interfaces for storage, navigation, permissions, analytics, and network access rather than import browser globals. A Hook that reads window, manages a DOM ref, or assumes browser focus is not platform-independent merely because its name starts with use. Platform adapters can supply those capabilities at the application boundary.

For UI, I would decide case by case whether shared primitives preserve the right experience. React Native supports platform-specific files and the Platform module; those are useful escape hatches, not failures of reuse. I would test the domain layer once and maintain platform-specific integration and end-to-end coverage for keyboard, touch, screen readers, navigation, and lifecycle changes. The measure of success is lower maintenance cost with good platform behavior, not the maximum possible percentage of shared lines.

118. How would you review a custom Hook API for correctness and maintainability?

I would ask whether the Hook encapsulates one understandable capability with clear inputs, outputs, and ownership. A name such as useChatConnection communicates more than a generic lifecycle wrapper. Its contract should explain which changes resynchronize it, which state is local to each caller, and whether any external shared resource is involved.

I would review Hook ordering, Effect dependencies, cleanup, immutable inputs and outputs, and asynchronous cancellation or stale-result handling. Suppressing dependency warnings to keep an unstable abstraction working is a warning sign. Derived values should usually be calculated during rendering rather than mirrored through Effects. Returned callbacks need a documented practical identity contract only where consumers depend on it.

Tests should exercise multiple callers, rapid input changes, unmounting during work, failures, and StrictMode setup and cleanup. Server rendering and hydration need explicit consideration if browser APIs are involved. I would also inspect the caller: a Hook can hide complexity while still forcing every consumer into awkward synchronization. A good extraction reduces repeated decisions without concealing when work happens or creating an undocumented singleton.

119. How would you diagnose a production bug that disappears when logging is added?

I would treat the disappearing bug as evidence of timing sensitivity, not proof that logging fixed it. Logging can change execution timing and what the developer observes, so I would capture minimal structured events with operation identifiers, versions, and timestamps rather than inspect mutable objects after the fact.

The investigation would look for stale closures, mutation during rendering, missing Effect cleanup, request responses applied out of order, and assumptions that a state setter updates the current render's captured value. I would separate these hypotheses from browser layout or third-party timing issues and reproduce against the production build and exact release configuration.

A deterministic test can replace real timing with controlled completion: start A, start B, finish B, finish A, then assert the intended state. I would also test unmounting or changing identity before an operation resolves. StrictMode can expose lifecycle mistakes, but it is not a production timing simulator. The fix should establish an invariant such as operation ownership or immutable snapshots, followed by targeted telemetry. Extra delays and forced synchronous rendering are not satisfactory fixes without a demonstrated causal explanation.

120. How would you lead a technical review of an unfamiliar React application?

I would start by running the application and tracing one critical user journey from route entry through data loading, interaction, mutation, and recovery. That gives the review a concrete backbone instead of becoming a list of stylistic preferences.

I would map rendering architecture, state ownership, data and cache boundaries, external subscriptions, and server authorization responsibilities. Then I would inspect production build configuration, dependency health, error telemetry, test reliability, accessibility, and measured performance. In the code, I would look for duplicated state, impure rendering, Effect chains, unstable identity, and broad client boundaries before recommending a new library.

Findings should separate verified defects, observed risks, and questions requiring evidence. Each high-priority item needs a reproducible example, user impact, proposed change, and a validation plan. I would pair with maintainers to understand constraints and preserve useful existing decisions. The deliverable would be a short risk-ranked backlog with owners and reversible first steps, not a wholesale rewrite proposal. A good technical review improves the team's ability to diagnose and change the application safely after the reviewer leaves.

React Documentation and Further Reading

The official React documentation identifies React 19.3 as the latest stable minor release. Version-sensitive features are labeled in context. Framework integration, server-component tooling, browser support, and React Compiler configuration must be checked separately; React Native is a distinct rendering platform.

Use these official React references to verify the concepts and APIs discussed in this guide. This is a curated reading list, not every page used during research. Source review and bounded local checks do not establish browser compatibility, production security, or end-to-end correctness for an application.

Team Sherlock.sh

Building fraud intelligence for live high-stake video interactions (financial discussions, hiring interviews, board meeting etc).

Catch candidate fraud before it costs you.

Sherlock AI helps you detect AI-assisted, proxy, and deepfake candidates, so every hiring decision is backed by evidence.

Ask AI about Sherlock.sh

Similar Posts