React Interview Questions
React is an open-source frontend JavaScript library for building web interface/components.
Beginner
Section titled “Beginner”1. What is Virtual DOM?
Section titled “1. What is Virtual DOM?”A Virtual DOM is an in-memory representation of actual DOM. Its used in React to efficiently compare the DOM changes and applies the diff to the actual DOM. It basically improves the performance of rendering process as directly manipulating actual DOM would be slower and expensive.
2. What is JSX?
Section titled “2. What is JSX?”JSX stands for JavaScript XML. Its a syntax extension for JavaScript that lets you write HTML like code directly inside your JavaScript files.
Tools like Babel or SWC convert JSX into standard JavaScript function calls (React.createElement()) before the code runs in a browser.
// For an example:<div>Hello World!</div>;
// is converted toReact.createElement("div", null, "Hello World!");3. Difference between class component and functional component?
Section titled “3. Difference between class component and functional component?”Class Components are es6 classes that extends React.Component and requires a render() function to return JSX. It rely on life cycle methods like componentDidMount(), componentDidUpdate() etc and manage state with this.state.
Functional Components are just general functions that takes props as input and return JSX. They rely on hooks (useEffect, useState, etc.) to handle life cycle events and manage state.
Functional component are preferred over class components as they are more cleaner and easier to understand. Class components are outdated but useful for error handling and can be used to create a ErrorBoundary component.
4. Stateless vs Stateful components
Section titled “4. Stateless vs Stateful components”Stateless Components are often called as Presentational Components. They don’t manage state on their own. Instead they receive data via props and use them while rendering the component.
const FormInput = ({ type = "text", label, name, value, onChange }) => { return ( <label> {label} <input type={type} name={name} value={value} onChange={onChange} /> </label> );};On the other hand A Stateful Component manages their state internally. They can update their state based on some user actions or events.
function UserForm() { const [formData, setFormData] = useState({ username: "", email: "", });
// Dynamically update the state whenever an input changes const handleChange = (event) => { const { name, value } = event.target; setFormData((prevData) => ({ ...prevData, [name]: value, })); };
const handleSubmit = (event) => { event.preventDefault(); console.log("Submitted State Data:", formData); // Send 'formData' to your backend API here };
return ( <form onSubmit={handleSubmit}> <FormInput label="Username:" name="username" value={formData.username} onChange={handleChange} /> <FormInput type="email" label="Email:" name="email" value={formData.email} onChange={handleChange} /> <button type="submit">Submit</button> </form> );}5. How to implement type checking in React?
Section titled “5. How to implement type checking in React?”We can implement type checking at two different stages. At compile time using TypeScript and at run time using PropTypes(mostly with JavaScript).
But as of React 19, PropTypes are deprecated and its recommended to use TypeScript as standard.
interface UserDataProps { name: string; age: number; email: `${string}@${string}.${string}`}
function UserData({ name, age, email }) { return ( <ul> <li>Name: {name}</li> <li>Age: {age}</li> <li>Email: {email}</li> </ul> )}6. What is the use of key prop?
Section titled “6. What is the use of key prop?”The primary purpose of the key prop in React is to give each element or component instance a stable, unique identity across renders. This identifier tells React’s reconciliation engine exactly which item in the list have been changed/added/removed, allowing React to optimize performance and preserve component state accurately.
Without key, React defaults to matching items by their array index. If you add a new item at the top of the list, React thinks all the items in the list have been changed, causing it to unnecessarily re-render the entire list.
By using an unique key, React compares old key with the new key and realizes that the existing items simply moved down and only mounts the single new item. This significantly improves the rendering performance.
We should not use index as keys as indexes will change when length of the list changes. This will result similar to using components without key.
function UserList({ users }) { return ( <ul> { users.map((user, index) => <li key={index}>{user.name}</li>) users.map((user) => <li key={user.id}>{user.name}</li>) } </ul> )}7. Controlled vs Uncontrolled component
Section titled “7. Controlled vs Uncontrolled component”In controlled component form data is managed by component state but in Uncontrolled component, form data is managed by the browser DOM.
| Feature | Controlled Components | Uncontrolled Components |
|---|---|---|
| Source of Truth | React State (useState) | The Browser DOM |
| Data Retrieval | Accessed instantly via the state variable | Pulled on demand using React Refs (useRef) or FormData |
| Value Tracking | Updates continuously via an onChange handler | Tracks internally; uses defaultValue for initial seeding |
| Re-renders | Re-renders the component on every keystroke | Does not trigger re-renders as the user types |
| Use Case | Complex validation, dynamic layouts, and instant UI feedback | Simple forms, non-React library integrations, and file uploads (<input type="file">) |
// Controlledfunction ControlledInput() { const [value, setValue] = useState();
return ( <input type="text" value={value} onChange={(e) => setValue(e.target.value)} /> );}
// Uncontrolledfunction UncontrolledInput() { const ref = useRef();
return <input type="text" ref={ref} />;}8. How to share data between React components?
Section titled “8. How to share data between React components?”In React, sharing data between components depends on the relationship between those components. The three primary approaches are passing data via Props, Lifting State Up and using the Context API.
-
Parent to Child(Using Props)
The most straightforward way to pass data is downward(unidirectional data flow) from a parent component to a child component using props.
// Parent Componentfunction Parent() {return <Child message="Hello from Parent!" />;}// Child Componentfunction Child({ message }) {// Destructuring the propreturn <h2>{message}</h2>;} -
Child to Parent(Using Callback Functions)
To send data up from a child to a parent, you still use props, but you pass a callback function from the parent to the child.
// Parent Componentfunction Parent() {const handleChildData = (data) => {console.log("Data from child:", data);};return <Child onSendData={handleChildData} />;}// Child Componentfunction Child({ onSendData }) {return (<button onClick={() => onSendData("Hello Parent!")}>Send to Parent</button>);} -
Between Sibling Components(Lifting State Up)
When two sibling components need to share the same changing data, you must lift the state up to their closest common parent component.
// Common Parent Componentfunction Parent() {const [sharedText, setSharedText] = useState("");return (<><InputSibling onChangeText={setSharedText} /><DisplaySibling text={sharedText} /></>);}
9. Explain state change cycle in react.
Section titled “9. Explain state change cycle in react.”In React, the state change cycle describes the exact sequence of events that occurs from the moment a state variable is modified to the moment the updated user interface (UI) is painted on the screen.
Modern React operates this cycle via a internal framework paradigm called Render and Commit, which divides the cycle into three main phases: Triggering, Rendering, and Committing.
-
Triggering a Render The cycle begins when a component’s state needs to change. This is usually sparked by user interactions (like clicking a button), network responses, or timers and a state updater function (like
setCountfrom theuseStatehook) orthis.setState()is called. Calling this function signals to React that the component’s UI is now out-of-date and needs to be recalculated. React queues this update. -
Rendering the Component Rendering means React is calling your component function to figure out what the UI should look like. React executes the component function from top to bottom using the new state values that returns a new tree of JSX elements(the Virtual DOM). For a state update, React compares this new JSX tree with the old tree from the previous render. It calculates exactly what changed.
-
Committing to the DOM Once React knows exactly what changed, it enters the commit phase and updates the actual browser DOM. Because of the diffing process in the rendering phase, React only changes the specific DOM nodes that actually need updating. If your state update didn’t change what the JSX outputs, React won’t touch the DOM at all.
Once the DOM is updated, React releases control back to the browser. The browser repaints the screen, and the user finally sees the updated state reflected visually.
10. What is props mirroring?
Section titled “10. What is props mirroring?”Props mirroring in React is an anti-pattern where a component copies an incoming prop directly into its own local state via useState or a constructor, leading to synchronized state issues.
Problem:
When you copy a prop straight into state upon initial render, the local state does not automatically update if the parent component changes that prop later on. This breaks the single source of truth principle in React.
// ANTI-PATTERN: Props Mirroringfunction ChildComponent({ initialCount }) { // The prop is only used once to seed the state const [count, setCount] = useState(initialCount);
// If initialCount changes in the parent, 'count' here won't change! return <button onClick={() => setCount(count + 1)}>{count}</button>;}Exception:
Sometimes developers want to initialize state from a prop. If you truly only need the initial value and will never sync it again, it is better to name the prop clearly (like initialValue or defaultCount) to signal that subsequent updates are ignored.
If you do need the component to stay synchronized with the prop, you should avoid storing it in state entirely and just use the prop directly inside your render logic, or compute it on the fly.
11. What are hooks in React and how to use them?
Section titled “11. What are hooks in React and how to use them?”Hooks are built-in functions in React that allow you use state and different other React features inside functional components. Before Hooks, developers had to write class components to manage a component’s lifecycle or local state. Hooks let you “hook into” these capabilities using simpler, cleaner JavaScript functions.
You can either use built-in hook or combine them to build your custom hook.
Some of the built-in hooks are useState, useEffect, useMemo, useCallback etc.
Simple custom hook using react
function useToggle(initialValue = false) { // 2. Use built-in React hooks inside const [value, setValue] = useState(initialValue);
// 3. Define the logic to modify the state const toggle = () => { setValue((prevValue) => !prevValue); };
// 4. Return the data and functions your component will need return [value, toggle];}Rules of Hooks
To ensure React can correctly track your state and lifecycle triggers, all Hooks must follow two main rules:
- Call Hooks only at the top level: Do not call Hooks inside loops, conditions, or nested functions.
- Call Hooks only from React functions: You can call them from React functional components or your own custom Hooks, but never from regular JavaScript functions or old class components.
12. What is useRef hook in React?
Section titled “12. What is useRef hook in React?”The useRef hook is a built-in React hook that allows you to reference a value that is not needed for rendering. It returns a mutable object with a single property called .current.
Unlike state useState, mutating the .current property does not trigger a component re-render. The value you store inside a ref persists across your component’s entire lifecycle.
import { useRef } from "react";
const myRef = useRef(initialValue);// Returns: { current: initialValue }Core Use Cases
-
Manipulating the DOM Directly
The most common use case for
useRefis to interact with HTML elements directly (similar to usingdocument.querySelectorin vanilla JavaScript). This is ideal for managing text input focus, scrolling to a specific element, or measuring element dimensions.import { useRef, useEffect } from "react";function FocusInput() {const inputRef = useRef(null); // 1. Create the refuseEffect(() => {// 3. Access and focus the DOM element via .currentinputRef.current.focus();}, []);return (<>// 2. Attach the ref to the JSX node<input ref={inputRef} type="text" /></>);} -
Storing Information That Doesn’t Change the UI
If a value needs to be remembered between renders but changing it shouldn’t cause the page to redraw, use a
ref. Common examples include storing interval/timeout IDs, tracking a render counter, or caching previous state values.import { useRef, useEffect } from "react";function Timer() {const timerIdRef = useRef(null);useEffect(() => {// Store the interval ID so we can clear it latertimerIdRef.current = setInterval(() => {console.log("Tick...");}, 1000);return () => clearInterval(timerIdRef.current);}, []);const stopTimer = () => {// Clears the interval without causing a re-renderclearInterval(timerIdRef.current);};return <button onClick={stopTimer}>Stop Log</button>;}
13. What is the useId hook and when should it be used?
Section titled “13. What is the useId hook and when should it be used?”The useId hook is a built-in React hook introduced in React 18 that generates unique, stable ID strings.
Its primary purpose is to create component-scoped identifiers that remain perfectly consistent across both the server and the client. This consistency solves a major problem in modern web apps: when using server-side rendering (SSR), functions like Math.random() or simple global counters will generate different IDs on the server than they do on the client, which completely breaks hydration. useId guarantees the exact same IDs are generated during both phases.
When to use useId?
You should use useId when you need to link DOM elements together for HTML accessibility attributes (ARIA). The most common use cases include:
- Form Labels: Linking a
- ARIA Attributes: Connecting descriptive elements with attributes like aria-describedby, aria-labelledby, or aria-controls.
When you build a reusable component that might be rendered multiple times on the same page, useId ensures that each instance gets its own unique ID pairs, preventing browser focus or screen reader clashes
import { useId } from "react";
function ProtectedField() { // Generates a unique, stable string ID (e.g., ":r0:") const hintId = useId();
return ( <div> <label htmlFor={hintId}>Password:</label> <input type="password" aria-describedby={hintId} id={hintId} /> <p id={hintId}>Must contain at least 8 characters.</p> </div> );}When not to use useId?
Developers often mistake useId as a general-purpose unique string generator. There are strict scenarios where you must avoid it.
- Do not use it to generate keys in a list: React key props must be generated from your actual data (like a database ID). Using useId in a loop will break React’s reconciliation engine during re-renders.
- Do not use it as a data cache key: It should not be used to generate keys for caching systems or the
use()hook. - Do not call it conditionally or inside loops: Like all React hooks,
useIdmust only be called at the very top level of your functional component.
14. How do you implement routing in React?
Section titled “14. How do you implement routing in React?”We can implement routing in React using the React Router(react-router-dom) library. The modern, recommended approach (React Router v6.4+) uses a Data Router configuration, which separates route definitions from the JSX tree to enable advanced data-fetching APIs. Alternatively, you can use the traditional JSX component-based declaration.
-
Approach A: The Modern Data Router
This approach defines your routes as a JavaScript configuration array and provides them to the app using
RouterProvider.import React from "react";import ReactDOM from "react-dom/client";import { createBrowserRouter, RouterProvider } from "react-router-dom";import Home from "./pages/Home";import About from "./pages/About";import NotFound from "./pages/NotFound";// Configure the paths and their corresponding componentsconst router = createBrowserRouter([{path: "/",element: <Home />,errorElement: <NotFound />, // Handles 404s/errors},{path: "/about",element: <About />,},]);ReactDOM.createRoot(document.getElementById("root")).render(<React.StrictMode><RouterProvider router={router} /></React.StrictMode>,); -
Approach B: Traditional JSX Component Routing
If you prefer configuring your routes directly inside your JSX component layout (e.g., inside App.jsx), wrap your layout with
BrowserRouter.import { BrowserRouter, Routes, Route } from "react-router-dom";import Home from "./pages/Home";import About from "./pages/About";import NotFound from "./pages/NotFound";function App() {return (<BrowserRouter><Routes><Route path="/" element={<Home />} /><Route path="/about" element={<About />} />{/* The "*" path catches any URL that doesn't match above */}<Route path="*" element={<NotFound />} /></Routes></BrowserRouter>);}export default App;
15. How do you programatically navigate between routes using React Router and share data between routes?
Section titled “15. How do you programatically navigate between routes using React Router and share data between routes?”We can use the useNavigate hook inside the functional components to trigger navigation inside a function or event handler.
import { useNavigate } from "react-router-dom";
function LoginButton() { const navigate = useNavigate();
const handleLogin = () => { // Perform authentication logic here... navigate("/dashboard"); };
return <button onClick={handleLogin}>Log In</button>;}How to Share Data Between Routes?
Depending on the type of data and how it needs to be accessed, there are four primary patterns for sharing data between routes:
-
Route State (Best for transient data) We can pass an arbitrary object inside the state property during navigation using the
navigate()function. This data isn’t visible in the URL bar and disappears if the user copies and pastes the link in a new tab.Sending
// Programmatically:navigate("/profile", { state: { userId: 123, username: "Alex" } });// Declaratively:<Link to="/profile" state={{ userId: 123, username: "Alex" }}>View Profile</Link>;Receiving
import { useLocation } from "react-router-dom";function Profile() {const location = useLocation();const { userId, username } = location.state || {}; // Fallback in case state is undefinedreturn <h1>Welcome, {username}!</h1>;} -
URL Parameters (Best for unique IDs & resource queries)
URL parameters embed data directly into the path string (e.g., /users/123). This is ideal for shareable, bookmarkable links.
Sending
navigate(`/users/${userId}`);Receiving
import { useParams } from "react-router-dom";function UserProfile() {const { id } = useParams(); // Retrieves '123'return <div>User ID: {id}</div>;} -
Query Strings / Search Parameters (Best for filtering & sorting)
Query strings append data to the end of the URL after a question mark (e.g., /shop?category=shoes&sort=price)
Sending
navigate("/shop?category=shoes");Receiving
import { useSearchParams } from "react-router-dom";function Shop() {const [searchParams] = useSearchParams();const category = searchParams.get("category"); // Retrieves 'shoes'return <div>Displaying items for: {category}</div>;} -
Global State Management (Best for persistent application state)
If data needs to be accessed by many different routes and persist throughout the entire session (like user authentication status or a shopping cart), passing data through the router becomes cumbersome. Instead, use global state:
- React Context API: Native to React and wraps your router structure to distribute global variables.
- State Libraries: External systems like Redux Toolkit or Zustand manage a central data store accessible from any view regardless of active route conditions.
Intermediate
Section titled “Intermediate”16. What is the difference between useEffect and useLayoutEffect in React?
Section titled “16. What is the difference between useEffect and useLayoutEffect in React?”The primary difference between useEffect and useLayoutEffect is when they execute in relation to the browser painting the screen. While both hooks share the exact same syntax and accept the same dependency arrays, they serve different performance and visual purposes.
useEffectruns asynchronously after the browser paints the screen. It non-blocking and won’t delay screen rendering. Primarily used for data fetching, event listeners and analytics.useLayoutEffectruns synchronously before the browser paints the screen. It blocks painting until the code finishes executing. Primarily used for measuring DOM nodes, custom animations, fixing UI flickering.
Lifecycle Flow
import { useState, useEffect, useLayoutEffect, useRef } from "react";
const defaultStyles = { background: "lightblue", margin: "10px auto", padding: "10px",};
export default function BasicExample() { const [count, setCount] = useState(0); const boxRef = useRef(null);
// 1. Synchronous (Runs BEFORE the screen updates) useLayoutEffect(() => { if (boxRef.current) { // Safely measure or tweak DOM elements directly before the user sees them console.log( "1. useLayoutEffect: Box width is", boxRef.current.offsetWidth, ); } }, [count]); // Fires every time count changes
// 2. Asynchronous (Runs AFTER the screen updates) useEffect(() => { // Perfect for tasks that don't affect the visual layout console.log("2. useEffect: Count updated to", count); document.title = `Count: ${count}`; }, [count]); // Fires every time count changes
return ( <div style={{ padding: "20px", textAlign: "center" }}> <div ref={boxRef} style={{ width: `${100 + count * 20}px`, ...defaultStyles }} > Box Width changes with count </div>
<p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> );}17. What are Pure Components?
Section titled “17. What are Pure Components?”A pure component in React is a component that renders the same output for the same state and props, helping optimize performance by skipping unnecessary re-renders.
They compare primitive values (like strings and numbers) by value, and complex values (like arrays and objects) by memory reference. If the shallow comparison finds no changes in props or state, React bypasses the render process entirely for that component. You can implement pure components depending on whether you are writing class-based or modern functional code.
import React from "react";
// Extending PureComponent automatically implements a shallow prop/state checkclass UserCard extends React.PureComponent { render() { // UserCard rendered only when props or state change! return <div>User: {this.props.name}</div>; }}
// React.memo makes this a pure component.// Without React.memo it will re-render every single time the parent component re-renders.const UserCard = React.memo(({ name }) => { // "UserCard rendered only when 'name' changes!" return <div>User: {name}</div>;});Common Pitfalls:
- Because pure components only do a shallow check, mutating a nested object or array directly in memory will fail to trigger a re-render. You must create a new copy of the object or array instead.
- Passing objects created inline on each render breaks purity because a new reference is generated every time.
// ❌ Breaks memoization because { id: 1 } is a new object on every render<MyComponent config={{ id: 1 }} />
- Inline Functions: Passing inline functions creates a new function instance on every render. Use the useCallback hook to preserve function references across renders.
// ❌ Creates a new function reference every render<MyComponent onClick={() => doSomething()} />
- The render phase must remain free of side effects and be completely predictable for identical inputs. The component must not use any guest/global variables that may cause unpredictable results.
18. What is the use of useCallback hook?
Section titled “18. What is the use of useCallback hook?”useCallback is a React Hook that caches (memoizes) a function definition between re-renders.
When a React component re-renders, every function defined inside it is recreated from scratch. In most cases, this creation is fast and negligible. However, useCallback allows you to preserve the same function instance across renders unless its dependencies change.
const cachedFn = useCallback(fn, dependencies);On the initial render, useCallback returns the function you passed. On subsequent renders, if the dependencies haven’t changed, React returns the exact same function instance from the previous render.
When Should You Use It?
-
Passing Callbacks to Optimized Child Components(Pure Components)
If a child component is wrapped in
React.memoto skip re-rendering when props don’t change, passing an inline function as a prop breaks that optimization—because a new function instance is created on every parent render.import { useState, useCallback, memo } from "react";// Child component skips re-rendering if its props haven't changedconst ExpensiveChild = memo(({ onClick }) => {return (<>...<button onClick={onClick}>Click Me</button></>);});function Parent() {const [count, setCount] = useState(0);const [otherState, setOtherState] = useState(0);// Without useCallback, handleClick would be a new reference on every render,// forcing ExpensiveChild to re-render even if count didn't change.const handleClick = useCallback(() => {console.log("Count:", count);}, [count]);return (<div><button onClick={() => setOtherState(otherState + 1)}>Update Unrelated State ({otherState})</button><ExpensiveChild onClick={handleClick} /></div>);} -
The Function is a Dependency in Another Hook If a function defined inside your component is listed in the dependency array of a
useEffect, wrapping it in useCallback prevents the effect from firing on every single render.function SearchResults({ query }) {const fetchData = useCallback(async () => {const response = await fetch(`/api/search?q=${query}`);return response.json();}, [query]); // Only recreated if 'query' changesuseEffect(() => {fetchData().then((data) => {// Handle data...});}, [fetchData]); // Effect only runs when fetchData reference changes} -
Custom Hooks
When building custom hooks, any functions returned by the hook should generally be wrapped in
useCallbackso consumers can safely use them inside their ownuseEffectdependency arrays or optimized components.
19. What is the use of useMemo hook in React?
Section titled “19. What is the use of useMemo hook in React?”useMemo is a React Hook that caches (memoizes) the calculated result of a function between re-renders, preventing expensive, resource-heavy calculations from running on every render.
-
Skipping Expensive Recalculations
React re-runs the entire component body whenever state or props change. If your component contains a heavy calculation, that calculation will execute again on every single render—even if the inputs to that calculation haven’t changed.
useMemowraps your calculation and only re-executes it when one of its specified dependencies changes.import { useMemo } from "react";function ProductList({ products, filterText }) {// Only recalculates when `products` or `filterText` changesconst filteredProducts = useMemo(() => {return products.filter((p) => p.name.includes(filterText));}, [products, filterText]);return <div>{/* Render filteredProducts */}</div>;} -
Preserving Referential Equality for Child Components
In JavaScript, object and array literals (
{},[]) are created with a new memory reference on every render. If you pass an unmemoized object or array as a prop to a child component wrapped in React.memo, the child will re-render every time because its prop reference changed.function Parent({ userId }) {// Without useMemo, 'options' gets a new memory reference every renderconst options = useMemo(() => ({userId,theme: "dark",}),[userId],);return <MemoizedChild options={options} />;} -
Stabilizing Dependencies for Other Hooks
If an object, array, or computed value is included in the dependency array of another hook (like
useEffectoruseCallback), wrapping that value inuseMemoprevents the dependent hook from firing unnecessarily on every render.
20. What is the use of useReducer hook in React?
Section titled “20. What is the use of useReducer hook in React?”The useReducer hook in React is an alternative to useState designed for managing complex or multi-step state logic within a component. It follows the Redux-style state management pattern, taking a state and an action, and returning a new state.
const [state, dispatch] = useReducer(reducer, initialState);reducer(state, action): A pure function that takes the currentstateand anactionobject, and returns the next state based on the action type.initialState: The initial value of your state.dispatch(action): A function used to trigger state updates by passing an action object (typically containing atypestring and optionalpayload).
import { useReducer } from "react";
const initialState = { count: 0 };
function reducer(state, action) { switch (action.type) { case "increment": return { count: state.count + 1 }; case "decrement": return { count: state.count - 1 }; case "reset": return { count: 0 }; default: throw new Error(`Unhandled action type: ${action.type}`); }}
function Counter() { const [state, dispatch] = useReducer(reducer, initialState);
return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: "decrement" })}>-</button> <button onClick={() => dispatch({ type: "increment" })}>+</button> <button onClick={() => dispatch({ type: "reset" })}>Reset</button> </div> );}Ideal Use Cases for useReducer
- Complex Forms: Managing forms with multiple inputs, validation states, touched/dirty flags, and submission states.
- Dependent State Updates: When the next state relies heavily on values in previous states or multiple fields need to update simultaneously (e.g., setting isLoading: true, error: null, and data: null on a fetch request).
- State Machines: Modeling finite states (e.g., idle -> loading -> success / error) to prevent illegal state combinations.
- Prop Drilling Mitigation with Context: Passing down a single static dispatch function via React Context to deep child components instead of creating and passing down multiple separate callback functions.
21. What is use of forwardRef() in React?
Section titled “21. What is use of forwardRef() in React?”In React, forwardRef() is a utility function used to pass a ref through a component to one of its child components or to an underlying DOM element.
Key Use Cases
- Direct DOM Access: Giving a parent component direct access to a child’s DOM node (e.g., to focus an input, scroll an element into view, or measure dimensions).
- Higher-Order Components (HOCs): Forwarding refs through wrapper or container components so the consumer can access the wrapped component’s underlying DOM element or instance.
- Component Libraries: Creating reusable, foundational UI elements (like buttons, inputs, or modals) that allow consumers to attach standard DOM refs.
import React, { useRef, forwardRef } from "react";
// 1. Wrap the child component with forwardRefconst CustomInput = forwardRef((props, ref) => { return <input ref={ref} className="input-styled" {...props} />;});
// 2. Parent component creates and passes the reffunction ParentComponent() { const inputRef = useRef(null);
const handleFocus = () => { // Directly focus the underlying HTML <input> inputRef.current.focus(); };
return ( <div> <CustomInput ref={inputRef} placeholder="Type here..." /> <button onClick={handleFocus}>Focus Input</button> </div> );}In React 19,
refis a regular prop on function components andforwardRefis deprecated.
22. What is strict mode in React?
Section titled “22. What is strict mode in React?”React Strict Mode (<React.StrictMode>) is a feature tool that wraps components to perform runtime checks and surface potential bugs during local development. It renders no visible UI and has zero impact on the production build.
import React from "react";import ReactDOM from "react-dom/client";import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));root.render( <React.StrictMode> <App /> </React.StrictMode>,);Key Benefits:
-
Detecting Impure Renders (Double Rendering)
React expects component rendering to be a pure calculation with no side effects. In development mode, Strict Mode intentionally calls component functions and state updaters twice to surface unexpected side effects.
If a component mutates an external variable during rendering, rendering it twice produces inconsistent results. Strict Mode exposes these side effects early before they cause subtle bugs in production.
-
Validating Effect Cleanup (Double Effect Execution)
In React 18+, Strict Mode mounts, unmounts, and remounts components on initial render in development.
If a useEffect sets up a subscription, timer, or EventListener without returning a cleanup function, remounting leads to memory leaks or duplicate listeners. It immediately exposes missing cleanup logic (e.g., duplicate WebSocket connections or double API calls in dev mode).
-
Detecting Deprecated or Unsafe APIs
Strict Mode warns in the browser console when your app or third-party dependencies use outdated React patterns, including:
- Unsafe Lifecycle Methods:
componentWillMount,componentWillReceiveProps,componentWillUpdate - String Ref API: Using
this.refs.myInputinstead ofuseRef()orcreateRef(). - Legacy Context API: Older context patterns replaced by
React.createContext - Deprecated
findDOMNode: An old escape hatch that breaks abstraction boundaries.
- Unsafe Lifecycle Methods:
23. What are render props in React?
Section titled “23. What are render props in React?”Render props is a technique in React for sharing code between components using a prop whose value is a function.
Instead of hardcoding what a component renders, the component accepts a function that returns a React element and calls that function inside its render logic.
// 1. Component that encapsulates state/logicfunction MouseTracker({ render }) { const [position, setPosition] = React.useState({ x: 0, y: 0 });
const handleMouseMove = (event) => { setPosition({ x: event.clientX, y: event.clientY }); };
return ( <div style={{ height: "100vh" }} onMouseMove={handleMouseMove}> {/* Pass state to the render prop function */} {render(position)} </div> );}
// 2. Parent component deciding how to present that statefunction App() { return ( <MouseTracker render={({ x, y }) => ( <h1> The mouse position is ({x}, {y}) </h1> )} /> );}Key Benefits
- Code Reuse: Share complex behavior (like tracking scroll, handling form state, or fetching data) across multiple components without duplicating code.
- Inversion of Control: The consumer determines how the UI looks based on the provided state, while the provider component handles how the state updates.
- Dynamic Rendering: You can pass dynamic data into the callback function to render completely different UI structures in different contexts.
While render props were the standard solution for sharing stateful logic prior to React 16.8, Custom React Hooks have largely replaced them for most use cases because Hooks avoid deep JSX nesting (“wrapper hell”).
24. How do you prevent http request race conditions in React?
Section titled “24. How do you prevent http request race conditions in React?”In React, we can use AbortController to cancel ongoing asynchronous tasks, most commonly fetch requests, when a component unmounts or when a user triggers a new request before the previous one finishes. This prevents memory leaks, race conditions, and unnecessary network bandwidth consumption.
import { useState, useEffect } from "react";
function SearchResults({ query }) { const [results, setResults] = useState({});
useEffect(() => { const controller = new AbortController(); let active = true; setResults({ status: "loading" });
const fetchResults = async () => { try { const response = await fetch(`/api/search?q=${query}`, { signal: controller.signal, });
const data = await response.json();
if (active) { setResults({ status: "success", data }); } } catch (e) { if (active) { setResults({ status: "error" }); } } };
fetchResults();
return () => { controller.abort(); // Cancels the in-flight request active = false; }; }, [query]);
return <div>{/* Render results */}</div>;}25. What is reconciliation?
Section titled “25. What is reconciliation?”Reconciliation is React’s process for determining what changed in your UI after a component re-renders, then applying only the necessary updates to the actual DOM. React compares the newly returned element tree with the previous one during the render phase, and applies the calculated changes during the commit phase.
For example:
function Counter() { const [count, setCount] = useState(0);
return ( <div> <h1>{count}</h1> <button onClick={() => setCount(count + 1)}>Increment</button> </div> );}When count changes:
- React calls the component again and creates a new UI description.
- React compares it with the previous description.
- It notices that only the text inside
<h1>changed. - React updates that text in the DOM instead of rebuilding the entire page.
What React compares?
React mainly uses these factors to decide whether to preserve or replace an element:
- Element type:
<div>and<span>are different types, so React replaces one with the other. - Position: Elements in the same position are generally compared with each other.
- Props: If the type is unchanged, React updates changed props while reusing the existing DOM node.
- Keys: In lists, keys help React identify which item was added, removed, or moved. Matching keys allow React to preserve the item’s state.
Example:
{ items.map((item) => <li key={item.id}>{item.name}</li>);}Use a stable, unique identifier such as item.id, rather than an array index when list items can be reordered or removed.
26. What is code splitting in React?
Section titled “26. What is code splitting in React?”Code splitting in React is the practice of dividing your application’s JavaScript bundle into smaller chunks that are downloaded only when needed. Instead of loading the entire app at startup, the browser loads the code required for the current page and fetches other features later. This can reduce initial load time and improve responsiveness.
React commonly uses JavaScript’s dynamic import() together with React.lazy and Suspense.
Example:
import { Suspense, lazy } from "react";
const SettingsPage = lazy(() => import("./SettingsPage"));
function App() { return ( <Suspense fallback={<p>Loading...</p>}> <SettingsPage /> </Suspense> );}Here:
SettingsPageis placed in a separate JavaScript chunk by the bundler.- That chunk is downloaded when React needs to render the component.
Suspensedisplays a fallback while the chunk loads.
Benefits and trade-offs
Benefits:
- Smaller initial JavaScript download.
- Faster startup, especially for large applications.
- Less JavaScript to parse and execute initially.
- Heavy features or rarely used pages can load on demand.
Trade-off: each additional chunk may require a network request, so splitting extremely small components can add unnecessary overhead. In practice, split at route boundaries or around large, optional features.
27. What’s new in React 19?
Section titled “27. What’s new in React 19?”React 19, released as stable on December 5, 2024, focuses on simplifying async mutations, forms, server rendering, and resource loading. The biggest changes are Actions, the new use API, improved Server Components support, and more capable React DOM primitives.
Major additions:
-
Actions for async mutations
React can now manage common mutation states—pending, errors, optimistic updates, and form submission, without as much manual
useStatecode.New capabilities include:
- Async functions inside transitions.
- Automatic pending-state tracking.
- Error handling through Error Boundaries.
- Automatic optimistic-update rollback.
- Functions passed directly to
<form action>andformAction. - Automatic resetting of uncontrolled forms after successful submission.
For Example:
<form action={submitAction}><input name="name" /><button disabled={isPending}>{isPending ? "Saving..." : "Save"}</button></form> -
New hooks
React 19 adds several APIs built around Actions:
API Purpose useActionState Runs an Action and exposes its latest result plus pending state useOptimistic Shows an immediate temporary UI while a request is pending useFormStatus Lets nested form components read the parent form’s pending status use Reads promises or context during render useis particularly notable because it can read a promise and suspend until it resolves:function Comments({ commentsPromise }) {const comments = use(commentsPromise);return comments.map(comment => (<p key={comment.id}>{comment.text}</p>));}Unlike ordinary hooks, use may be called conditionally, although promises should generally come from a Suspense-compatible framework or cache rather than being created during render.
-
Server Components and Server Actions
React 19 includes the Server Components features previously available through React’s Canary releases. Server Components can render at build time or on a server before client bundling, while Server Actions let client components call async server-side functions through the
"use server"directive. -
Metadata directly in components
You can now render document metadata wherever it makes sense in the component tree:
function BlogPost({ title }) {return (<article><title>{title}</title><meta name="description" content="A blog post" /><h1>{title}</h1></article>);}React hoists supported
<title>,<meta>, and<link>elements into the document<head>, including during streaming SSR and Server Component rendering. -
Built-in resource handling
React 19 adds support for:
- Stylesheets with precedence management.
- Async scripts rendered within components.
- Resource hints such as
preload,preinit,preconnect, andprefetchDNS. - Static pre-rendering APIs:
prerenderandprerenderToNodeStream. - These features help React coordinate resource loading with concurrent and streaming rendering.
Expert
Section titled “Expert”28. What is React Fiber?
Section titled “28. What is React Fiber?”React Fiber is React’s internal reconciliation architecture system that React uses to determine what changed in your component tree and how to update the UI efficiently and incrementally. It was introduced in React 16 as a rewrite of the older stack-based reconciler.
How it works
React represents each component or rendered element with a Fiber node. A Fiber stores information such as:
- The component or DOM element type.
- Its props and state.
- Its position in the component tree.
- Its relationship to parent, child, and sibling Fibers.
- Work that React needs to perform for that node.
React breaks rendering into smaller units of work instead of processing the entire tree in one uninterrupted call stack. This allows it to:
- Pause and resume rendering.
- Split large updates into chunks.
- Prioritize urgent updates.
- Reuse completed work.
- Abandon work that is no longer needed.
Render and commit phases
Fiber separates an update into two broad phases:
- Render phase: React calculates what should change. This work can be interrupted or restarted.
- Commit phase: React applies the completed changes to the DOM. This phase is kept consistent and is not partially applied.
For example, while React is rendering a large list, a more urgent input update can receive priority so typing remains responsive.
29. How would you investigate a React page that feels slow?
Section titled “29. How would you investigate a React page that feels slow?”Investigating performance issues in a React application requires a systematic approach to isolate whether the bottleneck is caused by rendering overhead, unnecessary re-renders, network/data fetching latency, or main-thread blocking JavaScript.
Here is a structured framework for diagnosing and fixing a slow React page.
-
Initial Profiling & Measurement
Before writing or changing any code, establish baseline metrics to pinpoint where time is being spent.A. Chrome DevTools (Performance Tab)
- Open DevTools in an Incognito Window (to disable extensions that distort performance).
- Record a Performance Profile while interacting with the slow feature.
- Check the Main Thread Flamechart:
- Long Tasks (Red Flags): Tasks taking longer than 50ms block user input.
- Call Stack: Look for heavy JavaScript execution, excessive layout thrashing, or expensive synchronous calculations.
- Check Core Web Vitals:
- LCP (Largest Contentful Paint): Indicates slow initial loading or unoptimized images/assets.
- INP (Interaction to Next Paint): Indicates slow response to clicks, taps, or keypresses.
- CLS (Cumulative Layout Shift): Highlights layout instability during load or state updates.
B. React Developer Tools Profiler
- Install the React DevTools extension.
- Open the Profiler tab and check “Record why each component rendered during profiling” in settings.
- Start recording, perform the slow interaction, and stop.
- Analyze the Flamegraph and Ranked Chart:
- Yellow/Orange bars: Components that took a long time to commit.
- Render Reasons: Hover over components to see why they rendered (e.g., “Props changed”, “Parent component rendered”, “Hook 3 changed”).
-
Identify the Root Cause
Once you have gathered profiler data, categorize the issue into one of four primary buckets:- Excessive/Un-necessary Render
- Cascading renders
- Inline reference churn
- Unsplit Context state
- Heavy Component Tree & DOM
- Large lists (>100 items)
- Heavy sync calculations
- Heavy layout / CSS recalculations
- Bundle Size & Loading Bottlenecks
- Large resources like images, fonts etc.
- Large bundle instead of multiple small bundles.
- Unoptimized bundles
- Heavy library imports
- Network & Data Fetching
- Waterfall API calls
- Uncached requests
- Excessive/Un-necessary Render
-
Targeted Fix Strategies
-
Bucket A: Excessive or Unnecessary Re-Renders
- Context Splitting: If using React Context, ensure frequently changing state is separated from static context. A single context change forces every consumer component to re-render.
- State Colocation: Push state down as close as possible to where it is actually consumed rather than keeping it high in the component tree.
- Stable References:
- Wrap expensive sub-trees or pure presentational components in
React.memo. - Pass stable callbacks using
useCallbackand memoize derived prop values usinguseMemowhen passing them down to memoized children. - Render Diagnostics: Use tools like
why-did-you-renderin development environments to log unnecessary re-renders automatically to the console.
-
Bucket B: Heavy Main Thread & DOM Operations
- List Virtualization: For rendering large datasets (hundreds or thousands of DOM nodes), implement windowing/virtualization using libraries like
react-windowor@tanstack/react-virtual. - Defer Non-Critical Updates:
- Use
useTransitionto mark non-urgent state transitions (e.g., filtering a long list while typing in a search bar), keeping input typing responsive. - Use
useDeferredValuefor values that update too frequently. - Offload Heavy Calculations: Move complex synchronous computations (data transformations, sorting) out of the render loop into Web Workers using
comlinkor memoize them withuseMemo.
- List Virtualization: For rendering large datasets (hundreds or thousands of DOM nodes), implement windowing/virtualization using libraries like
-
Bucket C: Bundle Size & Loading Bottlenecks
- Route & Component Code Splitting: Use
React.lazy()andSuspenseor framework-level dynamic imports (e.g., Next.jsdynamic()) to split code by route or non-critical modal/tab component. - Analyze Bundle Dependencies: Use
source-map-explorerorwebpack-bundle-analyzerto identify large packages (e.g., fulllodashimports, heavy date/charting libraries) and swap them for lighter alternatives or tree-shakeable imports. - Asset Optimization: Ensure images are lazily loaded, modern formats (
.webp,.avif) are served, and fonts are preloaded.
- Route & Component Code Splitting: Use
-
Bucket D: Data Fetching Latency
- Eliminate API Waterfalls: Shift sequential
useEffectfetches into parallel requests or move data fetching to the server level (Server Components / SSR loaders). - Client Caching & Stale-While-Revalidate: Use structured data-fetching solutions like
@tanstack/react-queryorSWRto handle caching, background updating, and optimistic UI updates.
- Eliminate API Waterfalls: Shift sequential
-
-
Verification & Prevention\
- Re-profile: Compare production-build performance traces before and after changes to verify measurable improvement (aim for lower Total Blocking Time and improved INP).
- Automate with Lighthouse / CI: Add Lighthouse CI checks to pull requests to prevent performance regressions on critical user journeys.
- Field Data Monitoring: Integrate Real User Monitoring (RUM) tools like Sentry, Datadog, or Vercel Analytics to track production Core Web Vitals (INP, LCP, CLS) across diverse device types and network conditions.
30. How would load large list as react component?
Section titled “30. How would load large list as react component?”To load a large list efficiently in a React component, you should avoid rendering all items at once. Instead, use virtualization (windowing) or pagination/infinite scroll so only a small subset of items is in the DOM at any time.
Core techniques
-
Virtualization / Windowing (best for very large lists)
Virtualization renders only the items currently visible in the viewport plus a small buffer, keeping the DOM size constant even for tens of thousands of rows.Popular libraries:
- react-window – lightweight, focused on lists and grids.
- react-virtualized – older, more feature-rich but heavier.
- tanstack/virtual – framework-agnostic virtualization that works well with React.
Basic pattern with react-window:
import { FixedSizeList as List } from 'react-window';function LargeList({ items }) {const Row = ({ index, style }) => (<div style={style}>{items[index].label}</div>);return (<Listheight={600} // visible height in pxitemCount={items.length} // total itemsitemSize={35} // height of each row in pxwidth="100%">{Row}</List>);}This way, even if
itemshas 100,000 entries, only ~20–30 DOM nodes exist at once. -
Pagination
Load and render data in pages (e.g., 20–50 items per page). This reduces initial render cost and network load if data comes from an API.Simple client-side pagination example:
function PaginatedList({ items, pageSize = 20 }) {const [page, setPage] = React.useState(0);const totalPages = Math.ceil(items.length / pageSize);const start = page * pageSize;const pageItems = items.slice(start, start + pageSize);return (<><ul>{pageItems.map(item => (<li key={item.id}>{item.label}</li>))}</ul><div><button disabled={page === 0} onClick={() => setPage(p => p - 1)}>Prev</button><span> Page {page + 1} of {totalPages} </span><button disabled={page >= totalPages - 1} onClick={() => setPage(p => p + 1)}>Next</button></div></>);}For server-side data, you’d fetch
?page=${page}&size=${pageSize}instead of slicing a local array. -
Infinite scroll
Similar to pagination but automatically loads more items as the user scrolls near the end. This keeps the initial render small while giving a seamless experience.Typical approaches:
- Use a library like
react-infinite-scroll-component. - Implement your own using
IntersectionObserveron a sentinel element at the bottom of the list.
Conceptually:
- Render first N items.
- When the sentinel becomes visible, append the next batch.
- Repeat until all data is loaded.
This avoids rendering everything at once but can still grow the DOM over time, so combine with virtualization if the list can become very large.
- Use a library like
Practical recommendations
- < 500 items, simple UI: plain
.map()in a component is usually fine. - 500–5,000 items: consider pagination or infinite scroll, virtualization if scrolling performance matters.
- > 5,000 items or frequent updates: use virtualization (
react-window/tanstack/virtual) as the default approach. - Always give each list item a stable key (e.g.,
item.id) to help React reconcile efficiently.
31. What is hydration and hydration mismatch in React?
Section titled “31. What is hydration and hydration mismatch in React?”What is hydration in React?
Hydration is the process by which React converts server-rendered (or statically pre-rendered) HTML into a fully interactive client side application by attaching event handlers and initializing state without recreating the existing DOM nodes.
In a typical Server-Side Rendering (SSR) setup:
- The server renders your React components to HTML and sends that HTML to the browser.
- The browser displays this HTML immediately, so the user sees content quickly.
- Once the JavaScript bundle loads, React “hydrates” that HTML. It walks the existing DOM tree, matches it against your component tree, and attaches event listeners and state.
Think of the server HTML as a “dry” skeleton, hydration is React pouring in the “water” of interactivity (events, state, effects) so the page becomes alive.
Hydration Mismatch
A hydration mismatch (or hydration error) happens when the HTML that React generates on the client during its first render does not exactly match the HTML that was rendered on the server.
Because hydration assumes the two trees are identical, any difference means React cannot safely reuse the server DOM as-is. When a mismatch is detected, React:
- Logs a warning/error showing the difference.
- Discards the mismatched part of the server-rendered tree (from the nearest Suspense boundary downward).
- Re-renders that part from scratch on the client.
This can cause visible issues like:
- Content flickering or shifting after load.
- Event handlers not working until after re-render.
- Inconsistent UI between initial paint and interactive state.
Common causes of hydration mismatches
Typical reasons the server and client renders differ include:
-
Using browser-only APIs during render
Accessing window, localStorage, document, or similar in code that runs on the server (e.g., in Next.js Server Components or during SSR) will produce different output on server vs client. -
Non-deterministic rendering
UsingMath.random(),Date.now(), or other values that change between server and client renders leads to different HTML. -
Conditional rendering based on environment
Rendering different content depending on whether code is running on the server or client (e.g.,typeof window === 'undefined'checks that change the rendered JSX). -
HTML structure differences
Invalid HTML nesting (e.g.,<div>inside<p>) that the browser auto-corrects differently than React expects can also trigger mismatches.
How to avoid or fix hydration mismatches
Common strategies:
-
Move browser-only logic to effects or client components
In frameworks like Next.js, put code that useswindow,localStorage, etc. insideuseEffector mark the component with"use client"so it only runs in the browser. -
Ensure render output is deterministic
Avoid random values or timestamps in the rendered markup unless they’re generated in a way that’s consistent between server and client (or deferred to after hydration). -
Use
suppressHydrationWarningcarefully
For known, safe one-off differences (like a timestamp that only affects text content), React allowssuppressHydrationWarningon specific elements, but this should be used sparingly and only when you understand the risk. -
Check component boundaries
Make sure Server Components and Client Components are separated correctly and that anything needing browser APIs is not rendered during the server pass.
32. How would you diagnose a hydration mismatch?
Section titled “32. How would you diagnose a hydration mismatch?”Diagnosing a React hydration mismatch comes down to pinpointing what rendered differently on the server versus the client during the initial render pass.
When a hydration error occurs, React compares the pre-rendered HTML sent from the server with the initial Virtual DOM tree generated in the browser. If there is a discrepancy, React throws a warning (or error in production/newer versions) and falls back to client-side rendering for that section.
-
Analyze the Developer Console Output
In React 18 and Next.js/SSR environments, React prints detailed warning logs in development. Look for messages like:Warning: Text content did not match. Server: "Hello Server" Client: "Hello Client"orHydration failed because the initial UI does not match what was rendered on the server.The browser console will show the exact component tree where the mismatch started. Inspect the highlighted element and look for the dynamic props, text nodes, or conditionals inside it.
-
Identify Common Hydration Mismatch Culprits
Most hydration errors stem from a few predictable patterns:-
Non-Deterministic Data (Dates, Timestamps, Random Values)
Using functions likeDate.now(),new Date().toLocaleTimeString(), orMath.random()during render causes the server and client values to differ.Move non-deterministic calculations into useEffect or state that updates only on the client
-
Browser-Only Globals (
window,localStorage,navigator)
Accessing window.innerWidth, localStorage, or user-agent detection directly in the render body means the server renders one branch (or undefined/fallback) while the client renders another.Access browser globals inside a useEffect hook or wrap the component in a client-only dynamic import.
-
Invalid HTML Nesting (SSR Engine Auto-Correction) Browsers automatically fix invalid HTML tags when parsing raw server HTML. For instance, placing a
<div>inside a<p>tag causes the browser to automatically split the<p>tag into two. When React attempts to hydrate, the DOM structure is already altered.<p><div>Content</div></p><table><div>Content</div></table> -
Server/Client Feature Flags & Auth States Rendering different components based on cookies or auth tokens that are missing or different between the SSR request and client state.
-
-
Debugging Techniques
A. Isolate with the React Developer Tools- Open React DevTools in your browser.
- Search for the component identified in the console stack trace.
- Inspect the props and state during initial render versus after hydration.
B. Temporary Suppress Warnings vs. Clean Fix
If a component must render client-only dynamic content and you accept a brief text shift, suppress the warning on specific elements:<span suppressHydrationWarning>{new Date().getFullYear()}</span>C. Create a
useIsMountedoruseMountedHook
For components dependent on client-side state (like dark mode orlocalStorage), use a mounted hook to delay client rendering until after initial hydration complete:import { useEffect, useState } from 'react';export function useIsMounted() {const [isMounted, setIsMounted] = useState(false);useEffect(() => {setIsMounted(true);}, []);return isMounted;}// Usage inside component:const isMounted = useIsMounted();if (!isMounted) return <Skeleton />;return <ClientOnlyContent />;
33. Explain different types of rendering mechanism in React and their use?
Section titled “33. Explain different types of rendering mechanism in React and their use?”React applications support several rendering strategies, each suited to different performance, SEO, and interactive needs.
| Mechanism | Description | Render Location | Ideal Use Cases |
|---|---|---|---|
| CSR (Client-Side Rendering) | HTML skeleton sent; browser downloads JS bundle to render content. | Browser | Internal dashboards, gated SaaS platforms, highly dynamic interactive apps. |
| SSR (Server-Side Rendering) | HTML generated on the server per user request with dynamic data. | Server (on request) | E-commerce product pages, news feeds, content requiring personalized SEO. |
| SSG (Static Site Generation) | HTML generated at build time and cached globally on a CDN. | Server (at build time) | Documentation sites, blogs, marketing landing pages, help centers. |
| ISR (Incremental Static Regeneration) | Rebuilds individual static pages in the background without a full rebuild. | Server (on background revalidation) | Large-scale content platforms, broad e-commerce catalogs, news portals. |
| RSC (React Server Components) | Components execute on the server and stream UI primitives without shipping JS. | Server (streaming to client) | Modern full-stack React applications (e.g., Next.js App Router). |
Deep Dive into Each Strategy
-
Client-Side Rendering (CSR)
In traditional CSR apps (like classic Create React App or Vite React setups), the server serves a bare HTML shell containing a script tag. The browser downloads the Javascript, executes React, pulls data via API calls, and updates the DOM.
- Pros: Smooth page transitions after initial load, reduced server execution burden (handled by the client).
- Cons: Slower initial page load (First Contentful Paint), poor SEO default performance without workarounds.
- Best Used For: Private dashboards, account settings, or apps where SEO is unnecessary and user interaction dominates.
-
Server-Side Rendering (SSR)
With SSR (supported by frameworks like Next.js and Remix), every incoming HTTP request causes the server to execute React, fetch fresh data, and compile the full HTML markup before sending it to the browser. The browser then hydrates the HTML to attach event listeners.
- Pros: Excellent for real-time SEO, fast First Contentful Paint (FCP), guaranteed fresh data on every request.
- Cons: Higher server load/costs, potential delay in Time to First Byte (TTFB) while waiting for server data fetching.
- Best Used For: Highly dynamic public pages like live inventory views, personalized user feeds, or personalized dashboards needing SEO.
-
Static Site Generation (SSG)
SSG builds every single route into flat HTML and JSON files during the continuous integration / build step. These pre-built files are deployed directly to a Content Delivery Network (CDN) edge.
- Pros: Blazing fast load times, minimal server costs, maximum reliability under high traffic spikes.
- Cons: Long build times for large sites; content remains stale until a new build and deployment occur.
- Best Used For: Marketing sites, blogs, portfolios, documentation hubs, and privacy policies.
-
Incremental Static Regeneration (ISR)
Popularized by Next.js, ISR combines SSG with dynamic cache revalidation. Pages are generated statically at build time, but when a revalidation interval expires (e.g.,
revalidate: 60), the next incoming request triggers a background regeneration without blocking the user.- Pros: Fast CDN speeds like SSG, but with scalable updates without redeploying the entire website.
- Cons: First visitor after cache expiration gets stale content while the background rebuild executes; non-trivial state consistency across distributed deployments.
- Best Used For: E-commerce sites with thousands of products, news archives, and large multi-author publication platforms.
-
Streaming SSR and Partial Hydration (advanced)
-
Streaming SSR:
The server streams HTML chunks to the client as they’re ready, instead of waiting for the entire page to render. This improves perceived load time, especially for large pages. -
Partial hydration / Islands architecture:
Only certain interactive “islands” of the page are hydrated with JavaScript; the rest remains static HTML. This reduces JS cost and improves performance.
-
-
React Server Components (RSC)
RSC splits component rendering at the architecture level. Server Components run exclusively on the server can directly query databases or access secrets and send serialized UI data to the browser with zero added JavaScript bundle size. Client Components handle traditional interactive state (
useState,useEffect).- Pros: Significantly smaller JavaScript bundles, fast rendering pipeline, direct access to backend resources directly inside the component tree.
- Cons: Requires specialized framework routing (e.g., Next.js App Router, Waku), stricter mental model around server/client boundaries.
- Best Used For: Modern full-stack React projects seeking optimal performance alongside server-side resource fetching.
34. Difference between Server and Client components?
Section titled “34. Difference between Server and Client components?”Server Components and Client Components (popularized by React 18+ and Next.js App Router) split modern UI rendering based on where the code executes and what resources it accesses.
Core Comparison
| Feature | Server Components (RSC) | Client Components |
|---|---|---|
| Execution Environment | Runs strictly on the server during build time or request time. | Prerendered on server, then hydrated and executed in the browser. |
| Bundle Size Impact | 0 KB JavaScript sent to browser. Code stays on the server. | Adds to the JavaScript bundle sent to the client. |
| Direct Backend Access | Direct access to databases, file systems, microservices, and API keys. | Must fetch data via network requests (APIs/Server Actions). |
| Interactivity & State | None. Cannot use useState, useEffect, or event listeners (onClick). |
Full interactivity (useState, useEffect, onClick, custom hooks). |
| Browser APIs | No access to window, document, localStorage, or DOM APIs. |
Full access to browser APIs (window, localStorage, navigator, etc.). |
| Opt-in Directive | Default in modern frameworks like Next.js App Router. | Explicitly declared using 'use client' at the top of the file. |
Key Concepts
-
Server Components
Server Components allow you to write UI that fetches data directly on the server without sending the rendering logic or heavy dependencies to the browser.
- Best used for: Data fetching, accessing backend resources, static UI sections (layouts, sidebars, text content), and keeping sensitive credentials (API tokens) off the client.
- Example:
// Runs entirely on the serverimport db from '@/lib/db';export default async function UserProfile({ userId }: { userId: string }) {const user = await db.user.findUnique({ where: { id: userId } });return (<div><h1>{user.name}</h1><p>{user.email}</p></div>);} -
Client Components
Client Components enable traditional client-side interactivity. While they are prerendered into static HTML on the server during the initial page load, their client JS executes in the browser to enable interactivity (hydration).
- Best used for: Event listeners (
onClick,onChange), state management (useState,useReducer), lifecycle hooks (useEffect), and browser-only APIs. - Example:
'use client'; // Opts this component and its imports into client executionimport { useState } from 'react';export default function LikeButton() {const [likes, setLikes] = useState(0);return (<button onClick={() => setLikes(likes + 1)}>Likes: {likes}</button>);} - Best used for: Event listeners (
How They Work Together
- Interleaving: Server Components can import and render Client Components directly.
- Passing Data: Server Components pass data down to Client Components as standard props (props must be JSON-serializable).
- Children Pattern: To render a Server Component inside a Client Component, pass the Server Component as a
childrenprop rather than importing it directly into the client file.
35. How would you solve data fetching waterfall across nested components?
Section titled “35. How would you solve data fetching waterfall across nested components?”A data fetching waterfall occurs when nested components trigger network requests sequentially rather than in parallel. Meaning Child B cannot start fetching its data until Parent A finishes fetching its data and renders.
Key Strategies to Eliminate Waterfalls
-
Parallelize at the Route / Server Level (Server Components / Framework Loaders)
If you are using modern React (Next.js App Router, Remix, or React Server Components), move data fetching to the server level or route level.
- React Server Components (RSC): Fetch data in parallel on the server using
Promise.all(). - Route Loaders (Remix / React Router 6.4+): Execute route-level loaders concurrently before components start rendering.
// Fetch parallel data at the top/server layer rather than nesting hooks in childrenasync function ProfilePage({ userId }: { userId: string }) {const [user, posts] = await Promise.all([getUser(userId),getUserPosts(userId)]);return <Profile user={user} posts={posts} />;} - React Server Components (RSC): Fetch data in parallel on the server using
-
Component-Level Preloading (Data Preloading Patterns)
If data fetching must stay on the client (e.g., standard Client-Side Rendering with
useEffector React Query), trigger the network request before or as soon as the parent starts loading, rather than waiting for child components to mount.// Define preloading functionfunction preloadUserPosts(userId: string) {// Initiates request and caches promise/result immediatelyqueryClient.prefetchQuery({ queryKey: ['posts', userId], queryFn: () => fetchPosts(userId) });}function ParentComponent({ userId }: { userId: string }) {// Preload child data immediately when parent renders/fetchespreloadUserPosts(userId);const { data: user } = useQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId) });return user ? <ChildComponent userId={userId} /> : <Loading />;} -
Render-as-You-Fetch with React Suspense
Instead of Fetch-on-Render (fetching in
useEffectafter mount), adopt Render-as-You-Fetch. Fire requests immediately upon interaction or route change, then pass the pending promise to<Suspense>.- Suspense boundaries allow sibling or nested trees to render up to the boundary without blocking the whole UI.
- React starts fetching data and rendering the component tree concurrently.
-
GraphQL / Relay Fragment Colocation
If using GraphQL, components define their data requirements via fragments, but the root route compiles these fragments into a single GraphQL query.
# Parent query fetches data for all nested children in 1 roundtripquery UserProfileQuery($id: ID!) {user(id: $id) {name...UserHeader_user...UserFeed_user}}
Summary
| Strategy | Ideal Use Case | Pros |
|---|---|---|
| Server Components / Route Loaders | Next.js, Remix, SSR apps | Solves waterfalls completely with 0 client JS overhead. |
Promise.all / Parallel Fetching |
Single component needing multiple resources | Simple, straightforward JavaScript. |
| Prefetching / React Query | Single-Page Apps (CSR) | Keeps component-level encapsulation without layout blocking. |
| GraphQL Fragments (Relay) | Complex, deeply nested component trees | Solves waterfall while preserving component-data colocation. |
36. What are the cons of using React context?
Section titled “36. What are the cons of using React context?”While React Context is a great built-in solution for passing global data through a component tree without manually passing props at every level, it introduces several trade-offs when overused or applied to the wrong use cases.
Core Disadvantages & Drawbacks
- Performance & Re-render Issues Every component consuming a context will automatically re-render whenever the context value object changes—even if that component only uses a small property of the state that didn’t change. Unlike dedicated state management libraries (like Zustand or Redux Toolkit), Context lacks fine-grained selectors out of the box.
- Architectural & Component Coupling Components that rely directly on context become tightly coupled to that specific provider hierarchy. This makes them significantly harder to reuse elsewhere in your application, isolate in component libraries, or render independently in unit tests without wrapping them in context mock providers.
- Provider Nesting (“Provider Hell”)
As an application grows, storing different slices of state (theme, auth, user settings, notifications, feature flags) in separate contexts can result in deeply nested component trees in your root component (
<AuthProvider><ThemeProvider><SettingsProvider>...). - Lack of DevTools & Debugging Support Unlike Redux, React Context doesn’t offer built-in action logging, state travel/time travel, or clear state diffs. Debugging where a state change originated or tracking complex state updates across multiple providers can quickly become difficult.
- Poor Suitability for High-Frequency State Context is designed for low-frequency updates (like theme changes, current user data, or UI locale). It is inefficient for fast, high-frequency updates—such as mouse position tracking, complex form inputs, or rapid real-time updates—due to the cascade of re-renders.
When to Use Context vs. Alternatives
| Feature / Use Case | React Context | External State Management (Zustand, Redux) |
|---|---|---|
| Best For | Low-frequency, app-wide values (Theme, Auth, Locale) | Complex, high-frequency, or deeply nested state |
| Re-render Optimization | Coarse (Re-renders all consumers) | Fine-grained (Selector-based subscriptions) |
| Setup Complexity | Very Low (Native to React) | Low to Moderate |
| Testing Isolation | Requires Provider wrappers | Easy to mock store state directly |
37. How would you design a re-usable component in React supporting both controlled and un-controlled behavior?
Section titled “37. How would you design a re-usable component in React supporting both controlled and un-controlled behavior?”To design a React component that seamlessly supports both controlled and uncontrolled states, follow the pattern used by standard HTML form elements (like <input>) and UI libraries such as Radix UI or React Spectrum.
The key pattern is internal state fallback: managing an internal state when the component is uncontrolled, while deferring entirely to the value prop when controlled.
-
Custom Hook (
useControllableState)Creating a dedicated hook keeps the state synchronization logic clean and reusable across multiple components.
import { useState, useCallback, useRef } from 'react';interface UseControllableStateProps<T> {value?: T;defaultValue?: T;onChange?: (value: T) => void;}export function useControllableState<T>({value: valueProp,defaultValue,onChange,}: UseControllableStateProps<T>): [T, (nextValue: T | ((prev: T) => T)) => void] {// Determine if the component is in controlled modeconst isControlled = valueProp !== undefined;// Track the internal state for uncontrolled modeconst [internalValue, setInternalValue] = useState<T>(defaultValue as T);// Store onChange in a ref to avoid stale closures in callbacksconst onChangeRef = useRef(onChange);onChangeRef.current = onChange;// Return controlled value if provided, otherwise internal stateconst value = isControlled ? valueProp : internalValue;const setValue = useCallback((nextValue: T | ((prev: T) => T)) => {// Calculate next value if a updater function was passedconst setter = (prev: T) => {const resolved = typeof nextValue === 'function'? (nextValue as (prev: T) => T)(prev): nextValue;// Notify parent callback in both controlled and uncontrolled modesonChangeRef.current?.(resolved);return resolved;};if (!isControlled) {setInternalValue(setter);} else {// In controlled mode, trigger callback using current valuePropconst resolved = typeof nextValue === 'function'? (nextValue as (prev: T) => T)(valueProp): nextValue;onChangeRef.current?.(resolved);}},[isControlled, valueProp]);return [value, setValue];} -
Implementing the Component
Here is a reusable
ToggleSwitchbuilt with the hook:import React from 'react';export interface ToggleProps {value?: boolean; // Controlled propdefaultValue?: boolean; // Uncontrolled proponChange?: (checked: boolean) => void;label?: string;}export const Toggle: React.FC<ToggleProps> = ({value,defaultValue = false,onChange,label = 'Toggle',}) => {const [isChecked, setIsChecked] = useControllableState({value,defaultValue,onChange,});const handleToggle = () => {setIsChecked(!isChecked);};return (<buttontype="button"role="switch"aria-checked={isChecked}onClick={handleToggle}className={`toggle-btn ${isChecked ? 'is-active' : ''}`}>{label}: {isChecked ? 'ON' : 'OFF'}</button>);}; -
Usage Examples
Uncontrolled Usage
The parent does not pass
value. State is managed internally, optional default set viadefaultValue.// Self-contained component<Toggle defaultValue={true} onChange={(val) => console.log('Toggled:', val)} />Controlled Usage
The parent passes
valueand controls updates viaonChange.const Parent = () => {const [isOn, setIsOn] = useState(false);return (<Togglevalue={isOn}onChange={(nextVal) => setIsOn(nextVal)}/>);};
Key Design Principles to Maintain
- Undefined Check (
value !== undefined): A component is controlled if and only ifvalueis explicitly passed (notnullorundefined). - Warn Against Switching Modes: Switching a component from controlled (
value={str}) to uncontrolled (value={undefined}) during its lifecycle causes bugs. In development, throw a warning ifisControlledchanges dynamic state. - Always Trigger
onChange: Fire theonChangeevent callback regardless of whether the component is controlled or uncontrolled so consumers can subscribe to state changes in both modes.
38. How do you manage Cache in React application?
Section titled “38. How do you manage Cache in React application?”Implementing Cache-Control for a React application depends on whether you are managing static assets (HTML, JavaScript, CSS, images) or dynamic API requests.
-
Caching Static Assets (Build Files)
For single-page applications (SPAs) built with React (Vite, Create React App, Webpack), static assets require two distinct caching strategies:
A. The Entrypoint (
index.html) — Never CacheThe
index.htmlfile must never be aggressively cached. It references bundled JS and CSS assets using content hashes (e.g.,main.a8f92c.js). If a browser cachesindex.html, users won’t receive app updates until their local cache expires.Header for
index.html:Cache-Control: no-cache, no-store, must-revalidatePragma: no-cacheExpires: 0no-cache: Tells the browser to validate with the server before using a cached copy.no-store: Prevents storing the HTML file anywhere (optional, but ensures complete cache bypass).
B. Hashed Assets (
/static/*,.js,.css, media) — Cache ForeverModern bundlers append unique hash strings to filenames whenever code changes. Since a filename change guarantees fresh content, these files can be cached safely for up to a year (the maximum allowed by HTTP specs).
Header for fingerprinted assets:
Cache-Control: public, max-age=31536000, immutablemax-age=31536000: Caches the file for 1 year ($365 \times 24 \times 60 \times 60$ seconds).immutable: Tells the browser the file content will never change, preventing conditional revalidation requests (304 Not Modified) on page reloads.
-
Web Server Configurations
Set these headers at your web server or CDN level rather than inside React.
Nginx Configuration
server {listen 80;server_name example.com;root /var/www/react-app/dist;# HTML entry point - revalidate alwayslocation / {try_files $uri $uri/ /index.html;add_header Cache-Control "no-cache, no-store, must-revalidate";}# Hashed static assets - cache immutably for 1 yearlocation ~* \.(?:css|js)$ {expires 1y;add_header Cache-Control "public, max-age=31536000, immutable";}# Media files (images, fonts)location ~* \.(?:jpg|jpeg|gif|png|ico|svg|woff2)$ {expires 1M;add_header Cache-Control "public, max-age=2592000";}}You can implement similar configuration for other server like Vercel, Netlify etc.
-
Caching API Responses inside React
To cache dynamic data fetches inside your React application (avoiding duplicate network requests across components), use dedicated data-fetching libraries like TanStack Query (React Query) or RTK Query.
Example with TanStack Query
import { useQuery } from '@tanstack/react-query';function UserProfile({ userId }: { userId: string }) {const { data, isLoading } = useQuery({queryKey: ['user', userId],queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),staleTime: 1000 * 60 * 5, // Data remains fresh for 5 minutes (no background refetch)gcTime: 1000 * 60 * 30, // Unused data remains in memory for 30 minutes});if (isLoading) return <div>Loading...</div>;return <div>{data.name}</div>;}Native
fetchwith Next.js (App Router / SSR React)If using React in a server context (e.g., Next.js), control headers via
fetchoptions:// Force-cache response (default)const staticData = await fetch('https://api.example.com/data', {cache: 'force-cache',});// Revalidate every 60 seconds (ISR)const revalidatedData = await fetch('https://api.example.com/data', {next: { revalidate: 60 },});
Summary
| Asset Type | Header Value | Purpose |
|---|---|---|
index.html |
no-cache, no-store, must-revalidate |
Ensures users instantly receive new application builds |
Bundled JS/CSS (/assets/*.js) |
public, max-age=31536000, immutable |
Maximizes performance via long-term CDN/browser cache |
| API Requests | Use staleTime via TanStack Query |
Minimizes redundant network requests on client re-renders |