FrontendDeveloper.in

React Interview Questions

  • Question 271

    When to use client and server components?

    You can efficiently build nextjs application if you are aware about which part of the application needs to use client components and which other parts needs to use server components. The common cases of both client and server components are listed below:

    Client components:

    1. Whenever your need to add interactivity and event listeners such as onClick(), onChange(), etc to the pages
    2. If you need to use State and Lifecycle Effects like useState(), useReducer(), useEffect() etc.
    3. If there is a requirement to use browser-only APIs.
    4. If you need to implement custom hooks that depend on state, effects, or browser-only APIs.
    5. There are React Class components in the pages.

    Server components:

    1. If the component logic is about data fetching.
    2. If you need to access backend resources directly.
    3. When you need to keep sensitive information((access tokens, API keys, etc) ) on the server.
    4. If you want reduce client-side JavaScript and placing large dependencies on the server.
  • Question 272

    What are the differences between page router and app router in nextjs?

    Next.js provides two different routing systems: the Page Router (traditional) and the App Router (introduced in Next.js 13). The App Router is built on React Server Components and offers more powerful features for modern web applications.

    Here are the main differences between them:

    FeaturePage RouterApp Router
    DirectoryUses pages/ directoryUses app/ directory
    RoutingFile-based routing with files like pages/about.jsFile-based routing with folders and special files like app/about/page.js
    ComponentsAll components are Client Components by defaultAll components are Server Components by default
    LayoutsCustom _app.js and _document.js for shared layoutsNative nested layouts using layout.js files
    Data FetchingUses getServerSideProps, getStaticProps, and getInitialPropsUses async/await in Server Components with native fetch
    Loading StatesManual implementation requiredBuilt-in loading.js for streaming and suspense
    Error HandlingCustom _error.js pageBuilt-in error.js for error boundaries at any level
    StreamingLimited supportBuilt-in support for streaming with Suspense
    Server ActionsNot availableNative support for server-side mutations
    MetadataUsing Head component from next/headNative Metadata API with metadata object or generateMetadata function
    RenderingSSR, SSG, ISR, and CSRSSR, SSG, ISR, CSR plus React Server Components

    Example of Page Router structure:

    pages/
    ├── index.js          // Home page (/)
    ├── about.js          // About page (/about)
    ├── _app.js           // Custom App component
    ├── _document.js      // Custom Document
    └── posts/
    └── [id].js       // Dynamic route (/posts/:id)
    

    Example of App Router structure:

    app/
    ├── page.js           // Home page (/)
    ├── layout.js         // Root layout
    ├── loading.js        // Loading UI
    ├── error.js          // Error UI
    ├── about/
    │   └── page.js       // About page (/about)
    └── posts/
    └── [id]/
    └── page.js   // Dynamic route (/posts/:id)
    

    Note: The App Router is recommended for new Next.js applications as it provides better performance, simpler data fetching patterns, and improved developer experience with React Server Components.

  • Question 273

    Can you describe the useMemo() Hook?

    The useMemo() Hook in React is used to optimize performance by memoizing the result of expensive calculations. It ensures that a function is only re-executed when its dependencies change, preventing unnecessary computations on every re-render.

    Syntax

    const memoizedValue = useMemo(() => computeExpensiveValue(arg), [dependencies]);
    
    • computeExpensiveValue: A function that returns the computed result.

    • dependencies: An array of values that, when changed, will cause the memoized function to re-run.

    If the dependencies haven’t changed since the last render, React returns the cached result instead of re-running the function.

    Let's exaplain the usage of useMemo hook with an example of user search and its respective filtered users list.

    Example: Memoizing a Filtered List

    import React, { useState, useMemo } from 'react';
    
    const users = [
    { id: 1, name: 'Sudheer' },
    { id: 2, name: 'Brendon' },
    { id: 3, name: 'Charlie' },
    { id: 4, name: 'Dary' },
    { id: 5, name: 'Eden' }
    ];
    
    export default function UserSearch({ users }) {
    const [searchTerm, setSearchTerm] = useState('');
    const [counter, setCounter] = useState(0);
    
    // Memoize the filtered user list based on the search term
    const filteredUsers = useMemo(() => {
    console.log("Filtering users...");
    return users.filter(user =>
    user.name.toLowerCase().includes(searchTerm.toLowerCase())
    );
    }, [searchTerm]);
    
    return (
    <h2>Counter: {counter}</h2>
    <button onClick={() => setCounter(prev => prev + 1)}>Increment Counter</button>
    
    <h2>Search Users</h2>
    <input
    type="text"
    value={searchTerm}
    onChange={(e) => setSearchTerm(e.target.value)}
    placeholder="Enter name"
    />
    
    <ul>
    {filteredUsers.map(user => (
    <li key={user.id}>{user.name}</li>
    ))}
    </ul>
    );
    }
    

    In the above example:

    • The filteredUsers list is only recomputed when searchTerm changes.
    • Pressing the "Increment Counter" button does not trigger the filtering logic again, as it's not a dependency.
    • The console will only log "Filtering users..." when the search term updates.
  • Question 274

    Can Hooks be used in class components?

    No, Hooks cannot be used inside class components. They are specially designed for function components. This is because hooks depend on the sequence in which they are called during a component’s render, something that's only guaranteed in functional components. However, both class and function components can coexist in the same application.

  • Question 275

    What is an updater function? Should an updater function be used in all cases?

    An updater function is a form of setState where you pass a function instead of a direct value. This function receives the previous state as an argument and returns the next state.

    The updater function expression looks like below,

    setCount(prevCount => prevCount + 1); // Safe and predictable
    

    Here, prevCount => prevCount + 1 is the updater function.

    In the React community, there's often a recommendation to use updater functions when updating state that depends on its previous value. This helps prevent unexpected behaviors that can arise from working with outdated or "stale" state.

    While using an updater function is a good habit, it's not always necessary. In most cases, React batches updates and ensures that the state is up-to-date at the beginning of the event handler, so you typically don’t run into stale state issues during a single synchronous event. However, if you’re doing multiple updates to the same state variable within a single handler, using the updater form ensures that each update correctly uses the latest state value, rather than a potentially outdated one.

    Example: Multiple Updates in One Handler

    function handleCount() {
    setCounter(a => a + 1);
    setCounter(a => a + 1);
    setCounter(a => a + 1);
    }
    

    In this example, a => a + 1 is an updater function. React queues these updater functions and applies them sequentially, each using the most recent state value. As a result, the counter will correctly increment by 3.

    In many cases, such as setting state based on user input or assigning static values, you don’t need the updater function:

    setName('Sudheer');
    
  • Question 276

    Can useState take a function as an initial value?

    Yes, useState can take a function as an initial value, and this is a useful feature in React called lazy initialization. This function is also known as initializer function.

    When you call useState(initialValue), you normally pass in a value directly:

    const [count, setCount] = useState(0);  // initial value is 0
    

    But if calculating that initial value is expensive or involves logic, you can pass a function that returns the value:

    const [count, setCount] = useState(() => {
    // This function only runs once — when the component first renders
    return expensiveComputation();
    });
    

    This function avoids doing heavy computation on every render. If you don't use this function form and invokes it directly, the function will run everytime the component renders and impact the performance. For example, the below usage is not recommended.

    const [count, setCount] = useState(expensiveComputation());
    
  • Question 277

    What types of values can `useState` hold?

    The useState hook accepts different types of values.

    • Primitives: number, string, boolean
    • Arrays
    • Objects
    • Functions
    • null or undefined

    But you needs to be cautious with reference types (objects/arrays) because React compares old and new values by reference, so direct mutations won't trigger a re-render. For example, the correct and wrong ways of state updates as shown below,

    user.name = "Sudheer"; //wrong way
    setUser(prev => ({ ...prev, name: 'Sudheer' })); //correct way
    
  • Question 278

    What happens if you call `useState` conditionally?

    As per rules of React Hooks, hooks must be called unconditionally. For example, if you conditionally call it:

    if (someCondition) {
    const [state, setState] = useState(0);
    }
    

    React will throw a runtime error because it relies on the order of Hook calls, and conditional logic breaks that order.

  • Question 279

    Is useState Synchronous or Asynchronous?

    The useState hook is synchronous, but state updates are asynchronous. When you call useState(), it runs synchronously and returns the state variable and setter function as tuple.

    const [count, setCount] = useState(0);
    

    This happens immediately during rendering. However, the state update function (setState) is asynchronous in the sense that it doesn't update the state immediately. React batches updates and applies them before the next render. You won’t see the updated value immediately after calling setState.

    Example:

    const [count, setCount] = useState(0);
    
    function handleClick() {
    setCount(count + 1);
    console.log(count); // ❗️Still logs the old value
    }
    

    The > console.log(count) prints the old value, because the update hasn’t happened yet.

    To see the updated state value, you can use useEffect() hook. It runs after the component has re-rendered.  By the time useEffect runs:

    • The component has been updated.
    • The state contains the new value.
    import React, { useState, useEffect } from 'react';
    
    function Counter() {
    const [count, setCount] = useState(0);
    
    const handleClick = () => {
    setCount(count + 1);
    console.log('Clicked count (old):', count); // Old value
    };
    
    useEffect(() => {
    console.log('Updated count:', count); // New value
    }, [count]); // Only runs when `count` changes
    
    return <button onClick={handleClick}>Count: {count}</button>;
    }
    
  • Question 280

    Can you explain how useState works internally?

    React’s hooks, including useState, rely on some internal machinery that keeps track of state per component and per hook call during rendering. Here's a simplified explanation of the internal mechanics:

    1. Hook List / Linked List

    • React maintains a linked list or array of "hook states" for each component.
    • When a component renders, React keeps track of which hook it is currently processing via a cursor/index.
    • Each call to useState() corresponds to one "slot" in this list.

    2. State Storage

    • Each slot stores:
    • The current state value.
    • A queue of pending state updates.

    3. Initial Render

    • When the component first renders, React:
    • Creates a new slot for useState with the initial state (e.g., 0).
    • Returns [state, updaterFunction].

    4. Updater Function

    • The updater function (setCount) is a closure that, when called:
    • Enqueues a state update to React's internal queue.
    • Schedules a re-render of the component.

    5. Re-render and State Update

    • On the next render:
    • React processes all queued updates for each hook slot.
    • Updates the stored state value accordingly.
    • Returns the new state to the component.

    6. Important: Hook Order

    • Hooks must be called in the same order on every render so React can match hook calls to their internal slots.
    • That’s why you can’t call hooks conditionally.

    The pseudocode for internal implementation of useState looks like below,

    let hookIndex = 0;
    const hooks = [];
    
    function useState(initialValue) {
    const currentIndex = hookIndex;
    
    if (!hooks[currentIndex]) {
    // First render: initialize state
    hooks[currentIndex] = {
    state: initialValue,
    queue: [],
    };
    }
    
    const hook = hooks[currentIndex];
    
    // Process queued updates
    hook.queue.forEach(update => {
    hook.state = update(hook.state);
    });
    hook.queue = [];
    
    // Define updater function
    function setState(action) {
    // action can be new state or function(state) => new state
    hook.queue.push(typeof action === 'function' ? action : () => action);
    scheduleRender(); // triggers React re-render
    }
    
    hookIndex++;
    return [hook.state, setState];
    }
    
  • Question 281

    What is `useReducer`? Why do you use useReducer?

    The useReducer hook is a React hook used to manage complex state logic inside functional components. It is conceptually similar to Redux. i.e, Instead of directly updating state like with useState, you dispatch an action to a reducer function, and the reducer returns the new state.

    The useReducer hook takes three arguments:

    const [state, dispatch] = useReducer(reducer, initialState, initFunction);
    
    • **reducer**: A function (state, action) => newState that handles how state should change based on the action.
    • **initialState**: The starting state.
    • **dispatch**: A function you call to trigger an update by passing an action.

    The useReducer hook is used when:

    • The state is complex, such as nested structures or multiple related values.
    • State updates depend on the previous state and logic.
    • You want to separate state update logic from UI code to make it cleaner and testable.
    • You’re managing features like:
    • Forms
    • Wizards / Multi-step flows
    • Undo/Redo functionality
    • Shopping cart logic
    • Toggle & conditional UI logic
  • Question 282

    How does `useReducer` works? Explain with an example

    The useReducer hooks works similarly to Redux, where:

    • You define a reducer function to handle state transitions.
    • You dispatch actions to update the state.

    Counter Example with Increment, Decrement, and Reset:

    1. Reducer function:

    Define a counter reducer function that takes the current state and an action object with a type, and returns a new state based on that type.

    function counterReducer(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:
    return state;
    }
     }
    
    1. Using useReducer: Invoke useReducer with above reducer function along with initial state. Thereafter, you can attach dispatch actions for respective button handlers.
    import React, { useReducer } from 'react';
    
    function Counter() {
    const initialState = { count: 0 };
    const [state, dispatch] = useReducer(counterReducer, initialState);
    
    return (
    <h2>Count: {state.count}</h2>
    <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
    <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    );
    }
    
    export default Counter;
    

    Once the new state has been returned, React re-renders the component with the updated state.count.

  • Question 283

    Can you combine **useReducer** with **useContext**?

    Yes, it's common to combine useReducer with useContext to build a lightweight state management system similar to Redux:

    const AppContext = React.createContext();
    
    function AppProvider({ children }) {
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
    <AppContext.Provider value={{ state, dispatch }}>
    {children}
    </AppContext.Provider>
    );
    }
    
  • Question 284

    Can you dispatch multiple actions in a row with useReducer?

    Yes, you can dispatch multiple actions in a row using useReducer but not directly in one call. You'd have to call dispatch multiple times or create a composite action in your reducer that performs multiple updates based on the action type.

    Example: Dispatching Multiple Actions You can define a custom function with dispatching actions one by one.

    function handleMultipleActions(dispatch) {
    dispatch({ type: 'increment' });
    dispatch({ type: 'increment' });
    dispatch({ type: 'reset' });
    }
    

    After that, you need to invoke it through event handler

    <button onClick={() => handleMultipleActions(dispatch)}>
    Run Multiple Actions
    </button>
    

    Note: You can also define a custom action type If you want multiple state changes to be handled in one reducer call.

    case 'increment_twice':
    return { count: state.count + 2 };
    

    Then dispatch

    dispatch({ type: 'increment_twice' });
    
  • Question 285

    Is dispatch from useReducer asynchronous and does it update state immediately?

    The dispatch function returned by useReducer is not asynchronous — it is a synchronous function call. When you call dispatch(action), React synchronously invokes your reducer with the current state and the action, computes the new state, and schedules a re-render. However, the state variable does not update immediately within the same render cycle. The updated state is only available in the next render.

    This behavior is similar to useState's setState — React batches state updates for performance optimization, meaning the component does not re-render immediately after each dispatch call. Instead, React processes all dispatched actions and re-renders once with the final state.

    Key Points

    1. dispatch is synchronous: The reducer runs immediately when dispatch is called.
    2. State update is not immediate in the current render: The state variable still holds the old value until the next render.
    3. React batches updates: Multiple dispatch calls within the same event handler result in a single re-render.
    4. Reducer is a pure function: It computes the new state without side effects.

    Example demonstrating that state does not update immediately

    import React, { useReducer } from 'react';
    
    function reducer(state, action) {
    switch (action.type) {
    case 'increment':
    return { count: state.count + 1 };
    default:
    return state;
    }
    }
    
    function Counter() {
    const [state, dispatch] = useReducer(reducer, { count: 0 });
    
    const handleClick = () => {
    dispatch({ type: 'increment' });
    console.log(state.count); // Still logs the OLD value (e.g., 0), not 1
    dispatch({ type: 'increment' });
    console.log(state.count); // Still logs the OLD value (e.g., 0), not 2
    };
    
    // After re-render, state.count will be 2 (both dispatches are processed)
    return (
    <button onClick={handleClick}>Increment Twice</button>
    );
    }
    

    In the above example, even though dispatch is called twice, state.count still reflects the previous value inside the event handler. React batches both dispatches and re-renders the component once with count: 2.

    How to read updated state after dispatch

    If you need the updated value right after dispatching, you have several options:

    1. Use useEffect to react to state changes:
    useEffect(() => {
    console.log('Updated count:', state.count);
    }, [state.count]);
    
    1. Compute the next state manually:
    const handleClick = () => {
    const nextState = reducer(state, { type: 'increment' });
    console.log('Next state will be:', nextState.count);
    dispatch({ type: 'increment' });
    };
    
    1. Use useRef to track the latest state:
    const stateRef = useRef(state);
    useEffect(() => {
    stateRef.current = state;
    }, [state]);
    

    Note: This behavior is by design in React. The dispatch function itself has a stable identity (it doesn't change between re-renders), which makes it safe to omit from useEffect dependency arrays.

Get LinkedIn Premium at Rs 399