Top React Interview Questions

ReactuseStateuseReducerState Management

useState and useReducer are both used to manage component state, but they have distinct use cases.


useState is simpler and ideal for managing individual pieces of state. It's great for simple state management scenarios.


useReducer is more appropriate for complex state logic, such as when state depends on previous states or if you need to handle more complex state transitions. It’s a better choice for managing multiple related states or performing complex updates.

 

Example use case for useReducer:


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 };
   default:
     return state;
 }
}
const Component = () => {
 const [state, dispatch] = useReducer(reducer, initialState);
 return (
   <div>
     <p>Count: {state.count}</p>
     <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
     <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
   </div>
 );
};

useReducer is ideal when the logic becomes more complex, such as in forms with multiple fields or managing nested states.

ReactContext APIState Management

Context API is a way to manage state globally in React without passing props through every level of the component tree. It provides a mechanism to share state across the entire app (or a part of it).

Context is ideal for global state such as user authentication, themes, language preferences, or other data that needs to be accessed by multiple components across the app.

 

Example:

const ThemeContext = React.createContext('light');
const ThemeProvider = ({ children }) => {
 const [theme, setTheme] = useState('light');
 return (
   <ThemeContext.Provider value={{ theme, setTheme }}>
     {children}
   </ThemeContext.Provider>
 );
};
const ThemedComponent = () => {
 const { theme, setTheme } = useContext(ThemeContext);
 return (
   <div style={{ background: theme === 'light' ? '#fff' : '#333' }}>
     <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</button>
   </div>
 );
};
// Wrap your app with the provider
const App = () => (
 <ThemeProvider>
   <ThemedComponent />
 </ThemeProvider>
);

Context is not a replacement for state management libraries like Redux but is useful for scenarios where you need lightweight global state management.

 

ReactHigher-Order ComponentsComponent Composition

Higher-order components (HOCs) are an advanced technique in React for reusing component logic. An HOC is a function that takes a component as an argument and returns a new, enhanced component. It's a pattern that emerges from React's compositional nature, allowing developers to share behavior between components without duplicating code. HOCs do not modify the original component passed into them. Instead, they return a new component with the desired functionality.

const withLog = (WrappedComponent) => {
 return (props) => {
   console.log("Component WrappedComponent.name is rendering");
   return <WrappedComponent {...props} />;
 };
};
const MyComponent = () => {
 return <div>My Component</div>;
};
const MyComponentWithLog = withLog(MyComponent);

In this example, withLog is the HOC. It takes MyComponent and returns a new component that logs a message to the console before rendering MyComponent. MyComponentWithLog now has this enhanced functionality.

 

HOCs are useful for various tasks, including:

Authentication: Wrapping components to check if a user is logged in.

Authorization: Controlling access to components based on user roles.

Data fetching: Providing data to components.

State management: Sharing stateful logic.

Styling and theming: Applying consistent styles.

Logging and error handling: Adding cross-cutting concerns.

ReactProject StructureScalability

For large-scale React applications, it’s essential to focus on modularity and separation of concerns. The key is to organize the code into clearly defined modules or features. 

Below are some practices we should follow.


Component-based Structure: Use atomic design principles—breaking UI into small, reusable components.


Folder Structure: Organize the app by features (rather than types) for better scalability and easier navigation, e.g., src/features/auth, src/features/products.


State Management: Use Context API or Redux for global state management, ensuring that state is consistent and easy to manage across different components.


Code Splitting: Use React.lazy() and Suspense for lazy loading of components to improve initial load time.


Performance Optimization: Use memoization (e.g., React.memo, useMemo) to prevent unnecessary re-renders and improve performance.
 

ReactError BoundariesError Handling

Error Boundaries are React components that catch JavaScript errors in their child components, log those errors, and display a fallback UI instead of crashing the app.

Example:

class ErrorBoundary extends React.Component {
 state = { hasError: false };
 static getDerivedStateFromError() {
   return { hasError: true };
 }
 componentDidCatch(error, errorInfo) {
   console.log('Error caught in boundary:', error, errorInfo);
 }
 render() {
   if (this.state.hasError) {
     return <h1>Something went wrong.</h1>;
   }
   return this.props.children;
 }
}
const BrokenComponent = () => {
 throw new Error('Error in component!');
 return <div>Broken Component</div>;
};
const App = () => (
 <ErrorBoundary>
   <BrokenComponent />
 </ErrorBoundary>
);

Error boundaries should be used at a component or page level to prevent the entire app from crashing due to a single error.

ReactPerformance OptimizationLarge Applications

We can follow the techniques below:

Memoization: Use React.memo to prevent unnecessary re-renders of functional components. This is helpful when the component props don't change often.

const MemoizedComponent = React.memo(MyComponent);

Lazy Loading: Implement code splitting using React.lazy() and Suspense for components that aren’t needed immediately, which reduces the initial loading time.

const LazyComponent = React.lazy(() => import('./LazyComponent'));

Avoid Re-renders: Use useMemo to memoize expensive computations or derived data. This can help prevent recalculations on every render.

const computedValue = useMemo(() => expensiveCalculation(input), [input]);

Virtualization: For rendering long lists, use react-window or react-virtualized to render only the visible items in the viewport, improving performance in lists with many items.

ReactPerformance OptimizationLarge Applications

We can follow the techniques below:

Memoization: Use React.memo to prevent unnecessary re-renders of functional components. This is helpful when the component props don't change often.

const MemoizedComponent = React.memo(MyComponent);

Lazy Loading: Implement code splitting using React.lazy() and Suspense for components that aren’t needed immediately, which reduces the initial loading time.

const LazyComponent = React.lazy(() => import('./LazyComponent'));

Avoid Re-renders: Use useMemo to memoize expensive computations or derived data. This can help prevent recalculations on every render.

const computedValue = useMemo(() => expensiveCalculation(input), [input]);

Virtualization: For rendering long lists, use react-window or react-virtualized to render only the visible items in the viewport, improving performance in lists with many items.

ReactControlled ComponentsUncontrolled ComponentsForms

In React, controlled and uncontrolled components represent two distinct ways of handling form data. Controlled components use React's state to manage form data, while uncontrolled components rely on the DOM to handle it. This difference primarily affects how data is tracked and updated within the component.

 

Uncontrolled Components
Uncontrolled Components are the components that do not rely on the React state and are handled by the DOM. So, in order to access any value that has been entered, we take the help of refs.

import React, { useRef } from "react";
function App() {
   const inputRef = useRef(null);
   function handleSubmit() {
       console.log(`Name: ${inputRef.current.value}`);
   }
   return (
       <div className="App">
           <form onSubmit={handleSubmit}>
               <label>Name :</label>
               <input
                   type="text"
                   name="name"
                   ref={inputRef}
               />
               <button type="submit">Submit</button>
           </form>
       </div>
   );
}
export default App;

Controlled Components
In React, Controlled Components are those in which form’s data is handled by the component’s state. It takes its current value through props and makes changes through callbacks like onClick, onChange, etc. A parent component manages its own state and passes the new values as props to the controlled component.

import { useState } from "react";
function App() {
   const [name, setName] = useState("");
   function handleSubmit() {
       console.log(`Name: ${name}`);
   }
   return (
       <div className="App">
           <form onSubmit={handleSubmit}>
               <label>Name:</label>
               <input
                   name="name"
                   value={name}
                   onChange={(e) =>
                       setName(e.target.value)
                   }
               />
               <button type="submit">Submit</button>
           </form>
       </div>
   );
}
export default App;

Controlled components are preferred in complex forms for validation and dynamic updates.

ReactReact FiberRendering

React Fiber is a reimplementation of React's core reconciliation algorithm, introduced in React 16. It aims to improve performance, especially for complex applications, by enabling incremental rendering. Instead of performing updates in one go, Fiber breaks down rendering into smaller, manageable units of work. This allows React to pause, prioritize, and resume work as needed, leading to smoother user experiences.
                                  
The following are the key features of React fiber.

Incremental Rendering:

It splits rendering work into chunks, allowing React to pause and resume, ensuring smoother UIs.

Prioritization:

Fiber enables React to prioritize updates, giving precedence to urgent tasks like user interactions.

Concurrency:

It lays the groundwork for concurrent mode, allowing React to handle multiple updates simultaneously without blocking the main thread.

Work Loop:

Fiber introduces a work loop that processes units of work in a cooperative scheduling manner, allowing pausing and resuming work.

 

React Fiber represents the nodes of the DOM tree as fiber nodes, linked together to form a fiber tree. This structure mirrors the component tree and facilitates efficient updates.

ReactState UpdatesRendering

 React throws an error:
 "Rendered more hooks than during the previous render."
You should not update state inside render because it causes infinite re-renders:

 

 Wrong:

const MyComponent = () => {
  const [val, setVal] = useState(0);
  setVal(1); // Bad
  return <div>{val}</div>;
};

 Instead of this, we should use useEffect.

ReactRe-renderingPerformance Optimization

React re-renders a component when its state or props change. However, frequent re-renders can impact the app’s performance, especially for large applications. To optimize re-renders, we can use the following strategies:


React.memo: Wrap functional components with React.memo to memoize the component’s output and avoid re-renders if the props haven’t changed.

const MemoizedComponent = React.memo(MyComponent);

useMemo: Use useMemo to memoize expensive computations, preventing them from being recalculated on every render.

const computedValue = useMemo(() => expensiveComputation(input), [input]);

useCallback: Use useCallback to memoize callback functions so they don’t change on every render.

const memoizedCallback = useCallback(() => { doSomething() }, []);

PureComponent: For class components, use React.PureComponent instead of React.Component, as it only re-renders when props or state change.

class MyComponent extends React.PureComponent { ... }

Virtualization: For large lists or tables, use react-window or react-virtualized to render only the visible items in the DOM.

 

ReactContext APIState ManagementComplex Applications

React Context is used to pass data through the component tree without having to pass props manually at every level. It’s particularly useful for managing global state or sharing data across deeply nested components.
Steps to Use Context API:
Create a Context: Use React.createContext() to create a context that will hold the state and provide it to components.

const MyContext = React.createContext();

Provider Component: Wrap your application (or part of it) with a Provider component, passing the value you want to share as a prop.

const MyProvider = ({ children }) => {
 const [state, setState] = useState("Initial State");
 return (
   <MyContext.Provider value={{ state, setState }}>
     {children}
   </MyContext.Provider>
 );
};

Consumer Component: Use the useContext hook to consume the context value in any functional component.

const MyComponent = () => {
 const { state, setState } = useContext(MyContext);
 return (
   <div>
     <p>{state}</p>
     <button onClick={() => setState("Updated State")}>Update</button>
   </div>
 );
};

Use Case: The Context API is often used for theming, user authentication, and language preferences where the same data is required by many components.

ReactEvent HandlingEvent Delegation

In React, event handling is based on the synthetic event system. This system is a wrapper around the browser’s native event handling mechanism, providing consistency across different browsers.
Event Binding: React binds events to the root DOM, meaning events are not directly attached to individual elements. Instead, React uses event delegation to handle all events through a single listener.

Synthetic Events: React’s SyntheticEvent is normalized across browsers, ensuring consistent behavior. It wraps around the native events like click, focus, etc.

const handleClick = (event) => {
 console.log(event.type); // Click
};
return <button onClick={handleClick}>Click Me</button>;

Event Delegation: React uses event delegation by attaching a single listener to the root DOM node (document). This reduces memory consumption and improves performance.

Example: When you click on a child element, React handles it through its delegated event system and invokes the appropriate handler.
 

ReactReact.memouseMemoPerformance

React.memo is a higher-order component (HOC) used to memoize functional components to avoid unnecessary re-renders. It works by shallowly comparing the props of the component and re-rendering only if the props have changed. It's especially useful for optimizing child components that receive props from their parent.

const MyComponent = React.memo(({ name }) => {
 console.log("Component re-rendered");
 return <div>{name}</div>;
});

useMemo is a hook that memoizes the result of a function. It helps optimize expensive calculations or derived values that don’t need to be recomputed every time the component re-renders.

const result = useMemo(() => expensiveComputation(input), [input]);

Difference:
React.memo memoizes the entire component, preventing re-renders based on prop changes.

useMemo memoizes values or calculations inside a component.

 

ReactCode SplittingPerformance

Code splitting is the practice of splitting your application’s bundle into smaller chunks that can be loaded on demand. This helps in reducing the initial load time of the app, as only the necessary code is loaded first, while the rest of the code is loaded as needed.
React provides several methods for code splitting:

React.lazy() and Suspense:

React.lazy() allows you to dynamically import components, while Suspense handles loading states until the component is ready.

const LazyComponent = React.lazy(() => import('./LazyComponent'));
const App = () => (
 <Suspense fallback={<div>Loading...</div>}>
   <LazyComponent />
 </Suspense>
);

React Router:

React Router supports code splitting by lazily loading route components.

Dynamic Imports:

Using import() syntax for dynamic imports allows you to split the code at a finer level of granularity.

Benefits:

  • Improved Initial Load Time: Only the necessary code for the initial view is loaded.
  • Reduced Bundle Size: The entire app is not loaded at once.
  • Better User Experience: Users don’t have to wait for the whole app to load.

ReactLifecycle MethodsClass Components

Class components have a set of lifecycle methods that can be used to control the behavior of components at various stages of their lifecycle:
componentDidMount: Called after the component is mounted (rendered to the screen). It’s typically used for fetching data.

componentDidMount() {
 console.log('Component mounted');
}

componentDidUpdate: Called after the component updates (e.g., after receiving new props or state).

componentDidUpdate(prevProps, prevState) {
 if (this.state.someValue !== prevState.someValue) {
   // Handle state change
 }
}

componentWillUnmount: Called before the component is unmounted from the DOM, useful for cleanup tasks like removing event listeners.

 

shouldComponentUpdate: Used to decide whether a component should re-render. It’s used for performance optimization.

 

ReactFormsUser Input

There are two types of form components in React:
Controlled Components:

React controls the form input via the state, ensuring that the state is always in sync with the input value.

const ControlledForm = () => {
 const [inputValue, setInputValue] = useState('');
 
 const handleChange = (e) => {
   setInputValue(e.target.value);
 };
 return (
   <form>
     <input type="text" value={inputValue} onChange={handleChange} />
   </form>
 );
};

Uncontrolled Components:

React does not directly control the input; instead, you use the ref to interact with the DOM element.

const UncontrolledForm = () => {
 const inputRef = useRef(null);
 const handleSubmit = () => {
   alert(inputRef.current.value);
 };
 return (
   <form>
     <input ref={inputRef} type="text" />
     <button type="button" onClick={handleSubmit}>Submit</button>
   </form>
 );
};

When to use which?
Controlled Components: Use when you need fine-grained control over form inputs (e.g., validation, dynamic updates).

Uncontrolled Components: Use when you don’t need to track the form state frequently.

 

ReactState UpdatesRendering

 React throws an error:
 "Rendered more hooks than during the previous render."
You should not update state inside render because it causes infinite re-renders:

 

 Wrong:

const MyComponent = () => {
  const [val, setVal] = useState(0);
  setVal(1); // Bad
  return <div>{val}</div>;
};

 Instead of this, we should use useEffect.

ReactControlled ComponentsUncontrolled ComponentsForms

In React, controlled and uncontrolled components represent two distinct ways of handling form data. Controlled components use React's state to manage form data, while uncontrolled components rely on the DOM to handle it. This difference primarily affects how data is tracked and updated within the component.

 

Uncontrolled Components
Uncontrolled Components are the components that do not rely on the React state and are handled by the DOM. So, in order to access any value that has been entered, we take the help of refs.

import React, { useRef } from "react";
function App() {
   const inputRef = useRef(null);
   function handleSubmit() {
       console.log(`Name: ${inputRef.current.value}`);
   }
   return (
       <div className="App">
           <form onSubmit={handleSubmit}>
               <label>Name :</label>
               <input
                   type="text"
                   name="name"
                   ref={inputRef}
               />
               <button type="submit">Submit</button>
           </form>
       </div>
   );
}
export default App;

Controlled Components
In React, Controlled Components are those in which form’s data is handled by the component’s state. It takes its current value through props and makes changes through callbacks like onClick, onChange, etc. A parent component manages its own state and passes the new values as props to the controlled component.

import { useState } from "react";
function App() {
   const [name, setName] = useState("");
   function handleSubmit() {
       console.log(`Name: ${name}`);
   }
   return (
       <div className="App">
           <form onSubmit={handleSubmit}>
               <label>Name:</label>
               <input
                   name="name"
                   value={name}
                   onChange={(e) =>
                       setName(e.target.value)
                   }
               />
               <button type="submit">Submit</button>
           </form>
       </div>
   );
}
export default App;

Controlled components are preferred in complex forms for validation and dynamic updates.

ReactPerformance OptimizationLarge Applications

We can follow the techniques below:

Memoization: Use React.memo to prevent unnecessary re-renders of functional components. This is helpful when the component props don't change often.

const MemoizedComponent = React.memo(MyComponent);

Lazy Loading: Implement code splitting using React.lazy() and Suspense for components that aren’t needed immediately, which reduces the initial loading time.

const LazyComponent = React.lazy(() => import('./LazyComponent'));

Avoid Re-renders: Use useMemo to memoize expensive computations or derived data. This can help prevent recalculations on every render.

const computedValue = useMemo(() => expensiveCalculation(input), [input]);

Virtualization: For rendering long lists, use react-window or react-virtualized to render only the visible items in the viewport, improving performance in lists with many items.

ReactReact FiberRendering

React Fiber is a reimplementation of React's core reconciliation algorithm, introduced in React 16. It aims to improve performance, especially for complex applications, by enabling incremental rendering. Instead of performing updates in one go, Fiber breaks down rendering into smaller, manageable units of work. This allows React to pause, prioritize, and resume work as needed, leading to smoother user experiences.
                                  
The following are the key features of React fiber.

Incremental Rendering:

It splits rendering work into chunks, allowing React to pause and resume, ensuring smoother UIs.

Prioritization:

Fiber enables React to prioritize updates, giving precedence to urgent tasks like user interactions.

Concurrency:

It lays the groundwork for concurrent mode, allowing React to handle multiple updates simultaneously without blocking the main thread.

Work Loop:

Fiber introduces a work loop that processes units of work in a cooperative scheduling manner, allowing pausing and resuming work.

 

React Fiber represents the nodes of the DOM tree as fiber nodes, linked together to form a fiber tree. This structure mirrors the component tree and facilitates efficient updates.

ReactCode SplittingPerformance

Code splitting is the practice of splitting your application’s bundle into smaller chunks that can be loaded on demand. This helps in reducing the initial load time of the app, as only the necessary code is loaded first, while the rest of the code is loaded as needed.
React provides several methods for code splitting:

React.lazy() and Suspense:

React.lazy() allows you to dynamically import components, while Suspense handles loading states until the component is ready.

const LazyComponent = React.lazy(() => import('./LazyComponent'));
const App = () => (
 <Suspense fallback={<div>Loading...</div>}>
   <LazyComponent />
 </Suspense>
);

React Router:

React Router supports code splitting by lazily loading route components.

Dynamic Imports:

Using import() syntax for dynamic imports allows you to split the code at a finer level of granularity.

Benefits:

  • Improved Initial Load Time: Only the necessary code for the initial view is loaded.
  • Reduced Bundle Size: The entire app is not loaded at once.
  • Better User Experience: Users don’t have to wait for the whole app to load.

ReactConcurrent ModeRendering

Concurrent Mode in React is a set of new features that help React apps stay responsive even when rendering large components or handling complex updates. It allows React to interrupt rendering tasks, work on high-priority updates first, and resume or abandon less critical tasks as needed. This makes the user interface feel smoother and more responsive, especially during time-consuming rendering operations.


Key Features and Concepts
Interruptible Rendering:
React can pause rendering to handle other events, like user input, ensuring the UI remains responsive.
Prioritizing Updates:
React can prioritize updates, processing important ones first and deferring less urgent ones.
Time Slicing:
Rendering work is broken into smaller chunks, allowing React to pause and resume rendering without blocking the main thread.
Suspense:
Components can "suspend" rendering while waiting for data, displaying a fallback UI until the data is ready.
Transitions:
Help differentiate between urgent and non-urgent updates, ensuring the UI remains responsive during transitions.
Automatic Batching:
Multiple state updates are grouped into a single re-render, improving performance. 


Benefits of concurrent mode
Improved User Experience:
Smoother and more responsive UI, especially during complex rendering tasks.
Better Performance:
Efficient handling of updates and rendering, leading to faster initial render times.
Enhanced Error Handling:
Improved error boundaries for handling errors during rendering, suspending, or resuming components. 


How it Works?
Concurrent Mode introduces a new rendering mechanism that allows React to work on multiple tasks concurrently. It does not mean that state updates are executed in parallel, but rather that they can overlap, with React prioritizing tasks based on their urgency.

 

import React, { useState, useTransition } from 'react';
function MyComponent() {
 const [isPending, startTransition] = useTransition();
 const [count, setCount] = useState(0);
 const handleClick = () => {
   startTransition(() => {
     setCount(c => c + 1);
   });
 };
 return (
   <div>
     {isPending && <p>Updating...</p>}
     <p>Count: {count}</p>
     <button onClick={handleClick}>Increment</button>
   </div>
 );
}

In this example, useTransition is used to mark the state update as non-urgent. If other urgent updates occur while this update is being processed, React can prioritize them, ensuring the UI remains responsive.

ReactMicro-FrontendsArchitecture

To implement micro-frontend architecture in a React app, you'll break down your application into smaller, self-contained modules, or micro-frontends, which can then be deployed and updated independently. This involves creating separate React apps for each micro-frontend, integrating them into a host application using techniques like Module Federation or iframe-based integration, and managing communication and styling consistency between them. 

ReactServer-Side RenderingE-commerceClient-Side Rendering

Server-Side Rendering (SSR) in React allows content to be rendered on the server before being sent to the client, which can provide faster load times and improved SEO.


Steps for SSR:
Set up a Node.js Server: Use express or any other Node.js server to handle server-side rendering.

ReactDOMServer: Use the ReactDOMServer.renderToString() method to render your React components to HTML on the server.

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';
app.get('*', (req, res) => {
 const content = ReactDOMServer.renderToString(<App />);
 res.send('
   <html>
     <body>
       <div id="root">content</div>
     </body>
   </html>
 ');
});

Hydration: On the client side, you will "hydrate" the HTML content rendered on the server using ReactDOM.hydrate() to make it interactive.

import ReactDOM from 'react-dom';
ReactDOM.hydrate(<App />, document.getElementById('root'));

Trade-offs of SSR vs CSR:


Advantages:

Faster initial page load: Server pre-renders HTML, so content is visible to users sooner.

Improved SEO: Search engines can crawl the fully rendered page.


Disadvantages:

Server Load: The server must render the page for each request, leading to higher CPU usage.

Complexity: SSR setup is more complex than CSR, requiring additional tooling (like Webpack, Babel, etc.).

Slower Interactivity: After initial HTML load, JavaScript still needs to be downloaded and executed, making the app interactive.
 

ReactSuspenseData FetchingState Management

React Suspense is a feature in React that lets you manage asynchronous data fetching in a more declarative way. It allows you to pause rendering until a component has finished loading its resources (e.g., fetching data or loading code).

 

Suspense with a lazy-loaded component:

const MyComponent = React.lazy(() => import('./MyComponent'));
const App = () => (
 <Suspense fallback={<div>Loading...</div>}>
   <MyComponent />
 </Suspense>
);

Suspense for Data Fetching:

Starting from React 18, Suspense can be used to manage data fetching in addition to lazy-loaded components. You can use React Query or other similar libraries to integrate Suspense for data fetching.

const { data, isLoading } = useQuery('fetchData', fetchData);
if (isLoading) {
 return <Suspense fallback={<div>Loading...</div>} />;
}
return <div>{data}</div>;

Integrating with Redux or Context API:
When combining Suspense with a state management solution like Redux or Context API, you can suspend data fetching in higher-order components and show loading states until the data is ready. For global state management, store the fetched data in the global state and propagate it across your app.


Dispatch actions to fetch data and store it in the Redux store.

Wrap components with Suspense and render them once the state is available.

import { useDispatch, useSelector } from 'react-redux';
const MyComponent = () => {
 const dispatch = useDispatch();
 const data = useSelector(state => state.data);
 if (!data) {
   dispatch(fetchData());  // Trigger data fetching
   return <div>Loading...</div>;
 }
 return <div>{data}</div>;
};

Context API:

Use Context to provide fetched data and allow child components to access it once it's available.

const DataContext = React.createContext();
const DataProvider = ({ children }) => {
 const [data, setData] = useState(null);
 useEffect(() => {
   fetchData().then(setData);
 }, []);
 return (
   <DataContext.Provider value={data}>
     {children}
   </DataContext.Provider>
 );
};
const MyComponent = () => {
 const data = useContext(DataContext);
 if (!data) return <div>Loading...</div>;
 return <div>{data}</div>;
};

ReactCustom HooksLocal StorageState Management

Custom hooks are a powerful way to extract and reuse logic in React. They allow you to encapsulate complex logic or state management in a reusable function, making components cleaner and easier to maintain.

Custom Hook for LocalStorage Synchronization:

The following custom hook stores a value in localStorage and synchronizes it with the state.

import { useState } from 'react';
function useLocalStorage(key, initialValue) {
 // Get from localStorage or use the initial value
 const storedValue = localStorage.getItem(key);
 const initial = storedValue ? JSON.parse(storedValue) : initialValue;
 const [value, setValue] = useState(initial);
 // Update localStorage when state changes
 const setStoredValue = (newValue) => {
   setValue(newValue);
   localStorage.setItem(key, JSON.stringify(newValue));
 };
 return [value, setStoredValue];
}
export default useLocalStorage;

Usage:

const MyComponent = () => {
 const [name, setName] = useLocalStorage('name', 'John Doe');
  return (
   <div>
     <h1>Hello, {name}</h1>
     <input
       type="text"
       value={name}
       onChange={(e) => setName(e.target.value)}
     />
   </div>
 );
};

ReactPerformanceProfilingConcurrency

Profiling Tools:

  • Use React DevTools Profiler to find re-render hotspots.
  • Use Lighthouse, Web Vitals, or Chrome Performance tab.


Common Fixes:

  • Avoid unnecessary re-renders via React.memo, useMemo, useCallback.
  • Debounce user input using lodash.debounce or requestIdleCallback.
  • Virtualize large lists with react-window or react-virtualized.
  • Use lazy loading for routes, components, and images.

ReactPreloadingPerformanceTTI

Chunk-based preloading enhances TTI by strategically loading code chunks. It involves splitting the application code into smaller, manageable chunks and preloading those that are essential for initial rendering and user interaction.
Implementation steps include:
Code Splitting:
Use dynamic imports or tools like Webpack to split the application into chunks. Route-based splitting is common, where each route or major section of the app becomes a separate chunk.
Identifying Critical Chunks:
Determine the chunks necessary for the initial view and interaction. This typically includes the main application logic, core components, and any immediately visible content.
Preloading Critical Chunks:
Employ <link rel="preload"> tags in the HTML <head> or use a library like react-loadable to preload critical chunks. Preloading hints to the browser to download these resources with high priority.
Lazy Loading Non-Critical Chunks:
Implement lazy loading for other chunks that are not immediately needed, such as components for less-used features or content below the fold. This ensures they are only loaded when required, reducing the initial load size.

// Example using dynamic import for code splitting and React.lazy for lazy loading
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./components/Home'));
const About = lazy(() => import('./components/About'));
const Dashboard = lazy(() => import('./components/Dashboard'));
function App() {
 return (
   <Router>
     <Suspense fallback={<div>Loading...</div>}>
       <Switch>
         <Route exact path="/" component={Home} />
         <Route path="/about" component={About} />
         <Route path="/dashboard" component={Dashboard} />
       </Switch>
     </Suspense>
   </Router>
 );
}
export default App;

ReactReduxContext APIZustandPerformance

Redux: Good for large apps with predictable state and complex state flows, but introduces boilerplate. Supports middleware and time-travel debugging.

Context API: Simpler, but causes propagation of re-renders down the component tree. Avoid using for high-frequency updates (like mouse position or timers).

Zustand: Minimal boilerplate with powerful features (selectors, middleware) and better re-render performance than Context API due to shallow comparisons and internal optimization.

Use Redux for shared, deeply nested state, Context for themes/auth, Zustand for local yet shareable states like drag positions or form wizard steps.

ReactFormsValidationPerformance
  • Use libraries like React Hook Form or Formik, which minimize re-renders by isolating field state.
  • Apply lazy validation (onBlur) or debounced validation.
  • Memoize components with React.memo.
  • Render sections conditionally using accordion or wizard UX.
  • Avoid top-down re-render by using field-level context or Zustand.

ReactAnalyticsCustom HooksTracking

Create a useAnalytics hook that sends events to a 3rd party.

const useAnalytics = () => {
 const trackEvent = (category, action, label = '') => {
   if (window.gtag) {
     window.gtag('event', action, {
       event_category: category,
       event_label: label,
     });
   }
 };
 return { trackEvent };
};

Usage:

const { trackEvent } = useAnalytics();
trackEvent('Form', 'Submit', 'Contact Us');

 You can also combine with useEffect to send pageview events on route changes via next/router.

ReactCustom HooksWebSocketReal-time

You can create a custom hook like useSocket to abstract socket connection logic.

import { useEffect, useState } from 'react';
import io from 'socket.io-client';
const useSocket = (url, eventName) => {
 const [data, setData] = useState(null);
 useEffect(() => {
   const socket = io(url);
   socket.on(eventName, (msg) => {
     setData(msg);
   });
   return () => socket.disconnect();
 }, [url, eventName]);
 return data;
};

Usage:

const liveData = useSocket('wss://my-server.com', 'new-message');

Advantages:

  • Encapsulated socket logic
  • Reusable across components
  • Easy to extend with event emitters, reconnects, or error handling
     

ReactCustom HooksDebouncingThrottling
import { useEffect, useState } from 'react';
const useDebounce = (value, delay = 300) => {
 const [debounced, setDebounced] = useState(value);
 useEffect(() => {
   const handler = setTimeout(() => setDebounced(value), delay);
   return () => clearTimeout(handler);
 }, [value, delay]);
 return debounced;
};

Usage:

const searchTerm = useDebounce(inputValue, 500);
import { useEffect, useRef, useState } from 'react';
function useThrottle(value, delay) {
 const [throttledValue, setThrottledValue] = useState(value);
 const lastExecuted = useRef(Date.now());
 useEffect(() => {
   const handler = setTimeout(() => {
     if (Date.now() - lastExecuted.current >= delay) {
       setThrottledValue(value);
       lastExecuted.current = Date.now();
     }
   }, delay - (Date.now() - lastExecuted.current));
   return () => clearTimeout(handler);
 }, [value, delay]);
 return throttledValue;
}

Usage:

function SearchInput() {
 const [search, setSearch] = useState('');
 const throttledSearch = useThrottle(search, 1000);
 useEffect(() => {
   if (throttledSearch) {
     // Call API or perform heavy logic
     console.log('Searching for:', throttledSearch);
   }
 }, [throttledSearch]);
 return (
   <input
     type="text"
     value={search}
     onChange={(e) => setSearch(e.target.value)}
     placeholder="Search..."
   />
 );
}

ReactMemory LeaksPerformanceScalability

Memory leaks often occur due to:

  • Uncleared subscriptions or timers (setInterval, setTimeout)
  • Stale references in closures
  • Detached DOM nodes retained by event handlers
  • Global state or refs not properly cleaned

Detection tools:
Chrome DevTools ? Performance ? Heap Snapshots

React DevTools ? Profiler

 

Example (bad pattern):

useEffect(() => {
 const id = setInterval(() => {
   console.log('leak?');
 }, 1000);
}, []);

Fix (cleanup):

useEffect(() => {
 const id = setInterval(() => {
   console.log('tick');
 }, 1000);
 return () => clearInterval(id);
}, []);

Best practices:
Always return a cleanup function in useEffect

Use AbortController with fetch

Remove event listeners and subscriptions
 

ReactState ManagementOfflineNetwork
  • Use Service Workers (via Workbox) to cache API responses.
  • Store transient state in IndexedDB or localStorage using useEffect.
  • Queue network requests when offline and retry them when connection restores (background sync pattern).
  • Implement a useNetworkStatus custom hook:
const useNetworkStatus = () => {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const goOnline = () => setIsOnline(true);
    const goOffline = () => setIsOnline(false);

    window.addEventListener('online', goOnline);
    window.addEventListener('offline', goOffline);

    return () => {
      window.removeEventListener('online', goOnline);
      window.removeEventListener('offline', goOffline);
    };
  }, []);
  return isOnline;
};

ReactWeb WorkersPerformanceData Processing

React runs on the main thread. To prevent UI blocking from heavy computations, offload work to Web Workers.

1. Create a worker file:

// worker.js
self.onmessage = function (e) {
 const result = heavyComputation(e.data);
 self.postMessage(result);
};
function heavyComputation(data) {
 return data.map(x => x * 100); // sample transformation
}

2. In React component:

import { useEffect } from 'react';
useEffect(() => {
 const worker = new Worker(new URL('./worker.js', import.meta.url));
 worker.postMessage([1, 2, 3]);
 worker.onmessage = (e) => {
   console.log('Result:', e.data);
 };
 return () => worker.terminate();
}, []);

 Use libraries like comlink to simplify Worker messaging with Promises.