React Interview Questions & Answers
Curated React interview questions with detailed answers, grouped by topic. Search, revise and get interview-ready.
React Introduction
A JavaScript library, created by Meta, for building user interfaces declaratively out of reusable components. You describe what the UI should look like for a given state, and React handles updating the actual DOM to match.
Imperative code describes the exact steps to reach a result (manually manipulating the DOM). Declarative code describes the desired end result for a given state, and lets the framework figure out how to get there — the approach React embraces.
No — React is a UI library. Routing, data-fetching conventions, and build tooling come from separate libraries or frameworks (like React Router or Next.js) built around it.
React Setup
Vite — it offers a fast dev server and build setup with minimal configuration for client-only React apps.
It attaches React to a DOM node (typically a single root <div>) and renders the given component tree into it — the entry point that starts a React app.
A development-only wrapper that helps surface common mistakes, by intentionally double-invoking certain functions and warning about deprecated patterns. It has no effect on production builds.
JSX
Regular JavaScript function calls, like React.createElement(type, props, children) — browsers never see JSX directly; a build tool transforms it before the code runs.
Because JSX attributes ultimately become JavaScript object properties passed to createElement, and class is a reserved word in JavaScript, so React uses className instead.
JSX compiles to a single function call tree, so a component can only return one root node. A Fragment (<>...</>) lets you return multiple sibling elements without adding an extra, meaningless DOM node.
Components & Props
A capital letter — this is how React (and JSX) distinguishes a custom component (<MyComponent />) from a built-in HTML tag (<div />).
No — props are read-only. Data flows one way, from parent to child; a child that needs to trigger a change should call a callback function passed down as a prop instead.
It automatically contains whatever JSX is nested between a component’s opening and closing tags, letting a component wrap and render arbitrary content passed by its parent.
Rendering Lists & Keys
It helps React identify which items changed, were added, or were removed between renders, so it can update the DOM and preserve state correctly for the right items.
If the list is reordered, filtered, or has items inserted/removed, index-based keys can cause React to associate state with the wrong item, since the index no longer reliably identifies the same logical item across renders.
No — key is a special value used internally by React for reconciliation, and is never accessible as props.key inside the component.
Conditional Rendering
Through plain JavaScript — if statements, ternary expressions, and logical operators — since JSX is just an expression returned from a function, not a special templating syntax.
Since 0 is falsy but still a valid, renderable value, React renders the literal "0" on screen instead of nothing. Using a comparison like count > 0 avoids this.
Nothing is rendered for that component — it’s a valid way to conditionally render "no UI" at all.
Handling Events
The version with parentheses calls handleClick immediately during render and passes its return value as the handler, instead of passing a reference to the function to be called later on click.
By wrapping the call in an inline arrow function, e.g. onClick={() => handleClick(id)}, so the function is only invoked when the event actually fires.
A browser’s default action for that event — most commonly, preventing a form submission from triggering a full page reload.
useState & State
An array with exactly two items: the current state value, and a setter function used to update it and trigger a re-render.
No — it schedules a re-render for the next render cycle. Reading the state variable immediately after calling its setter still shows the old value from the current render.
Q24. Why should you use the functional update form, setCount(prev => prev + 1), for sequential updates?
It guarantees each update reads the latest state value, even when several updates are queued together — using the plain value form (setCount(count + 1)) repeatedly can incorrectly reuse the same stale value.
Forms & Controlled Inputs
Its value is driven entirely by React state rather than the DOM’s own internal state — the value comes from a state variable, and onChange updates that state on every keystroke.
checked, paired with an onChange handler reading event.target.checked.
Q27. What is the advantage of managing multiple form fields with a single state object instead of one useState call per field?
It reduces boilerplate for forms with many fields, and each change handler can update just the changed key using the input’s name attribute and a spread update.
Component Composition
Passing a prop down through several intermediate components that don’t use it themselves, purely to reach a deeply nested child that does.
By accepting JSX through regular named props (like left and right), effectively creating multiple "slots" a parent can fill.
It avoids threading props through components that don’t actually need them, and keeps each component focused on a single responsibility.
Styling in React
Class names are automatically scoped to the component that imports them, avoiding naming collisions with identically-named classes used elsewhere in the app.
A JavaScript object with camelCase property names (e.g. { backgroundColor: "red" }), not a CSS string.
The style prop only sets static inline CSS properties and has no way to express pseudo-classes (:hover) or @media rules.
Fragments & Portals
It lets a component return multiple sibling elements without adding an extra, meaningless wrapping DOM node — useful when an extra <div> would break CSS layouts like Flexbox or Grid.
Whenever the Fragment needs a key — for example, when a Fragment is returned inside a .map() over a list — since the shorthand syntax cannot accept attributes.
They render content into a different DOM node (often near the end of <body>), letting it visually escape an ancestor’s overflow: hidden or z-index stacking context, while events still bubble through the React tree normally.
useEffect & Side Effects
The effect runs exactly once, after the component’s first render — equivalent in spirit to componentDidMount in class components.
It acts as a cleanup function, called by React right before the effect runs again (if dependencies changed) and once more when the component unmounts.
The effect can end up reading a stale, outdated value captured from an earlier render instead of the current one, since the effect only re-runs when a listed dependency changes.
Component Lifecycle
Mounting (first render), updating (re-rendering due to new props/state/context), and unmounting (removed from the DOM).
useEffect(() => { ... }, []) — an effect with an empty dependency array runs only once, after the first render.
Setup and cleanup logic for the same concern can live together in one function, instead of being split across componentDidMount and componentWillUnmount.
Lifting State Up
Moving shared state to the closest common ancestor of the components that need it, then passing it back down as props to each of them.
Through a callback function passed down as a prop, which the child calls — the parent then updates its own state, and the new value flows back down through props.
When the shared state needs to reach components many levels apart, forcing props to thread through several unrelated intermediate components — often a sign to reach for Context instead.
Context API
Sharing data across a component tree — like theme, locale, or the logged-in user — without manually threading props through every intermediate component (prop drilling).
useContext(SomeContext).
Every component consuming that context re-renders, even if it only uses part of the value — a key limitation to keep in mind for frequently-changing, performance-sensitive state.
useRef & Refs
No — unlike state, mutating a ref does not cause the component to re-render.
Getting direct access to the underlying DOM node — for example, to call .focus() on an input, something not achievable purely declaratively through props.
During the component’s render body — refs should only be read or written inside event handlers or effects, since accessing them during rendering makes the render unpredictable.
Custom Hooks
Its name must start with "use" — this tells React and linting tools (like eslint-plugin-react-hooks) that it follows the Rules of Hooks.
No — each call to the hook creates its own completely independent copy of the internal state. Only the logic is reused, not the data.
Q54. What is the difference between reusing logic with a custom hook and reusing UI with a component?
A custom hook shares stateful behavior (state and side effects), while a component shares rendered JSX markup — they solve different problems and are frequently combined.
Hooks Overview
"use" — this convention is how React and linters (eslint-plugin-react-hooks) recognize a function as a hook subject to the Rules of Hooks.
Only call hooks at the top level (never inside loops, conditions, or nested functions), and only call hooks from React function components or other custom hooks.
React matches each hook call to its internal state by the order it was called in, not by name — calling hooks conditionally would shift that order between renders and corrupt the association between hook calls and their state.
useLayoutEffect
useLayoutEffect fires synchronously immediately after DOM mutations but before the browser paints, while useEffect fires asynchronously after the paint has already happened.
It prevents visible flicker when a component needs to measure the DOM and synchronously adjust layout based on that measurement, since the correction happens before anything is painted.
Because it runs synchronously and blocks the browser from painting until it finishes, a slow useLayoutEffect can make the whole page feel less responsive.
useImperativeHandle
Customize exactly what value is exposed when a parent component attaches a ref to it, instead of exposing the entire underlying DOM node.
It keeps a clean boundary between the component and its parent, avoiding tight coupling to internal implementation details the parent shouldn’t need to know about.
No — function components can accept ref directly as a regular prop, though useImperativeHandle itself still works the same way regardless.
useTransition
Mark a state update as low-priority, so React can keep the rest of the UI responsive and interrupt the transition if something more urgent (like further typing) comes in.
That the transition is still being processed in the background, useful for showing a subtle loading indicator without blocking the rest of the UI.
React abandons the stale, in-progress transition and starts fresh with the latest value, so the UI never shows outdated results from an interrupted update.
useDeferredValue
A version of the given value that lags behind during urgent updates and catches up once React has spare rendering capacity.
It adapts to the actual device and current workload instead of waiting a fixed amount of time, which can be faster or slower than any hardcoded delay.
When you don’t directly control the update that changes a value — for example, when an expensive component is fed by a fast-changing prop rather than a state update you trigger yourself.
useId
Generating unique, SSR-safe IDs for accessibility attributes (like label/input pairs) that stay consistent across multiple instances of the same component on a page.
It produces a different value on the server than on the client, causing a hydration mismatch — exactly the class of bug useId is designed to avoid.
No — list keys should come from the data itself; useId is meant for generating a component instance’s own accessibility-related IDs.
useSyncExternalStore
State that lives outside of React entirely — a browser API, a custom event emitter, or a third-party store — rather than state already managed with useState or useReducer.
subscribe registers a callback to be notified of store changes (returning an unsubscribe function), and getSnapshot returns the store’s current value.
It guarantees a consistent, non-stale value even under React’s concurrent rendering, which a manual effect-based subscription can’t fully guarantee.
useDebugValue
Only how a custom hook’s value is labeled in React DevTools — it has no effect on the component’s actual behavior or rendered output.
Built-in hooks like useState already display their value in DevTools, while a custom hook’s internal state would otherwise appear unlabeled and opaque.
It defers expensive formatting so it only runs when DevTools is actively inspecting that hook, avoiding wasted work during normal rendering.
The use() Hook
It can be called conditionally and inside loops, unlike every other hook, which must always be called unconditionally at the top level.
It suspends the component until the Promise resolves, integrating directly with Suspense without needing manually-managed loading state.
Yes — it can read Context similarly to useContext, but with the added ability to do so conditionally, which useContext cannot.
useOptimistic
Show an expected result immediately in the UI before an async action actually finishes, making interactions like likes or sending a message feel instant.
React reverts to the real state that was passed into useOptimistic, with no manual rollback code required.
Actions that succeed the vast majority of the time — for actions with a meaningfully high failure rate, a visible loading state may communicate uncertainty more honestly.
useActionState
Both the action’s current result/state (like a validation error or success message) and a pending flag, without separate useState calls for each.
The previous state and the submitted form data.
Q87. What is the main benefit of useActionState over manually wiring useState for pending/result state?
It consolidates a very common cluster of form-handling boilerplate — pending tracking and result/error state — into a single hook tied directly to the action.
useFormStatus
The submission status of the nearest parent <form>, from a component rendered inside it — with no props needed.
It always returns the default, non-pending status — a form cannot read its own status through this hook; it must be called from a component nested inside the form.
It avoids prop drilling an isPending value down to nested components like a reusable submit button, which can instead read the form’s status directly.
useReducer
The current state and a dispatch function used to send actions describing what happened.
The current state and an action object (typically with a type and optional payload) describing the update that should happen.
When state updates are complex and depend on several related sub-values at once — centralizing the update logic in one reducer function keeps it easier to follow and test than scattering it across many event handlers.
useMemo & useCallback
It re-runs an expensive calculation only when one of its listed dependencies changes, reusing the cached result on every other render.
A function reference — returning the same function instance across renders as long as its dependencies haven’t changed, instead of creating a brand-new function every render.
Without a stable function reference, a newly-created function prop on every parent render defeats React.memo’s shallow prop comparison, causing the memoized child to re-render anyway.
Error Boundaries
It catches JavaScript errors thrown during rendering in its child component tree, and shows a fallback UI instead of letting the error crash the entire app.
No — it must be a class component, since getDerivedStateFromError and componentDidCatch have no direct function-component/hook equivalent yet.
No — error boundaries only catch errors during rendering, in lifecycle methods, and in constructors. Errors in event handlers or async code need to be handled separately, typically with try/catch.
React Router
It navigates client-side, updating the URL and swapping the rendered route without triggering a full page reload.
useParams(), which returns an object of the current route’s parameters.
Q102. How does file-based routing (used by frameworks like Next.js) differ from React Router’s declarative <Route> elements?
File-based routing derives the URL structure automatically from the folder/file structure of the project, rather than requiring routes to be explicitly declared with <Route> components.
Performance Optimization
It wraps a component so it skips re-rendering when its props haven’t changed (via a shallow comparison), even if its parent re-renders.
If a prop is a newly-created object, array, or function on every parent render, the shallow comparison always sees it as different — which is why React.memo is often paired with useMemo/useCallback.
To render only the items currently visible in the viewport for a very long list, recycling DOM nodes as the user scrolls, instead of rendering every item at once.
Suspense & Lazy Loading
It renders temporary UI shown while the content wrapped inside the Suspense boundary is still loading (e.g. a lazy component still downloading, or data still resolving).
A Suspense boundary, which shows its fallback while the component’s code chunk is downloading.
No — Suspense only handles the "still loading" state. Errors need to be handled by an error boundary, which is commonly used alongside Suspense.
Server vs Client Components
On the server only — its code never ships to the browser, and it cannot use hooks like useState or useEffect.
"use client" at the top of the file.
No — data and rendered output flow from server to client, not the reverse. A Server Component can render a Client Component as a child, passing serializable props, but not vice versa.
Data Fetching Patterns
To prevent a race condition — if a dependency (like an id) changes quickly, an older, slower request could resolve after a newer one and incorrectly overwrite fresh data with stale data.
Automatic caching by key, request deduplication, background refetching, and built-in race condition handling — removing the need to hand-write that logic in every component.
It can fetch data directly with async/await during rendering, with no client-side request or loading state needed for that initial render.
Testing React Components
Behavior from the user’s perspective — what’s rendered on screen and how it responds to interaction — rather than internal implementation details like component state.
getByRole, since it also verifies the element is genuinely accessible (a real button, heading, etc.), unlike getByTestId which relies on an artificial attribute.
findBy* queries return a promise and wait for the element to appear, while getBy* throws immediately if the element isn’t present yet — essential for testing components that fetch data or update after a delay.
React 19 Features
It can be called conditionally, unlike other hooks which must always run unconditionally at the top level — and it can read a Promise, suspending the component until it resolves.
Built-in tracking of pending state and the action’s result/error, without manually wiring separate useState calls for each of those concerns.
It shows an expected result immediately in the UI before an async action actually finishes, automatically reverting to the real value if the action fails.
State Management Libraries
Managing large, frequently-updated, widely-shared application state more efficiently than Context alone, which re-renders every consumer on any change.
Server state needs caching, refetching, and staleness handling — concerns that libraries like TanStack Query specialize in, separately from tools focused on purely client-side UI state.
No — many apps are well served by useState, useReducer, and Context alone; a dedicated library is worth adding once prop drilling or Context re-renders become an actual, measured problem.
React Best Practices
A separately-stored derived value can get out of sync with the values it depends on. Computing it fresh during render guarantees it’s always correct.
Loading, error, and empty — beyond the "happy path with data," these are the states real-world conditions (slow networks, failed requests, no results) actually produce.
When it exists only to react to a state change that was already caused by your own event handler — that logic usually belongs directly in the event handler instead of a separate effect.