React interview questions and answers for 2025

hero image

React.js Interview Questions for Freshers and Interediate Levels

1.

What are the key features of React.js?

Answer

React.js is a JavaScript library used for building user interfaces, primarily for single-page applications (SPAs). Some of its key features include:

  1. Component-Based Architecture – Applications are built using reusable, independent components, making development more modular and maintainable.
  2. Virtual DOM – React uses a lightweight copy of the DOM to optimize updates, improving performance by reducing direct manipulations of the real DOM.
  3. Declarative UI – React makes UI development more predictable by using a declarative approach, where UI updates automatically reflect changes in state.
  4. Unidirectional Data Flow – Data flows from parent to child components through props, making state management more predictable.
  5. Hooks – Introduced in React 16.8, Hooks (useState, useEffect, etc.) allow functional components to manage state and side effects without using class components.
  6. JSX (JavaScript XML) – A syntax extension that allows writing UI structures using HTML-like code within JavaScript.
  7. React Fiber – A reconciliation algorithm introduced in React 16 to improve rendering performance and handle UI updates efficiently.
  8. State Management – React provides built-in state management (useState, useReducer) and integrates well with external libraries like Redux or Context API.
  9. Server-Side Rendering (SSR) Support – With frameworks like Next.js, React can improve SEO and initial load performance through SSR.

 

These features make React.js efficient, flexible, and widely adopted for modern web and mobile application development.

2.

How does React differ from other JavaScript frameworks like Angular or Vue?

Answer

React.js differs from Angular and Vue in several key ways:

  1. Library vs. Framework – React is a library for building UI components, focusing on the view layer, whereas Angular is a full-fledged framework that includes built-in solutions for routing, state management, and dependency injection. Vue is more flexible, sitting between React and Angular, offering both a UI library and framework-like features.
  2. Rendering Approach – React uses a Virtual DOM for efficient UI updates, while Angular uses real DOM with change detection, and Vue also leverages a Virtual DOM but with a more lightweight reactivity system.
  3. Component-Based Architecture – All three use a component-driven approach, but React relies on JSX (JavaScript XML), while Angular and Vue use templating languages (HTML + directives).
  4. State Management – React uses useState, useReducer, and Context API for state management, but often requires external libraries like Redux or Recoil for complex state handling. Angular has RxJS and Services, and Vue has Vuex or Pinia for state management.
  5. Learning Curve – Angular has a steeper learning curve due to TypeScript, decorators, and a complex ecosystem. React has a moderate learning curve, especially with Hooks replacing class components. Vue is considered easier to learn due to its simpler syntax and clearer documentation.
  6. Performance – React and Vue are generally faster than Angular due to their efficient rendering mechanisms, but Angular is well-suited for large-scale enterprise applications where structure and built-in features matter.
  7. Ecosystem & Use Cases:
    • React is widely used in startups and large-scale applications (e.g., Facebook, Instagram, Netflix).
    • Angular is preferred for enterprise applications (e.g., Google, Microsoft).
    • Vue is popular in small-to-medium applications and companies that prioritize simplicity (e.g., Alibaba, Xiaomi).
  8. Data Flow – React follows a unidirectional data flow, meaning data moves in a single direction from parent to child components, making state management more predictable. In contrast, Angular supports bidirectional data binding, allowing automatic synchronization between the model and the view, which simplifies form handling but can introduce complexity in larger applications.

 

Overall, React provides flexibility and a strong ecosystem, making it a preferred choice for developers building scalable and interactive single-page applications (SPAs).

3.

What is JSX, and why is it used in React?

Answer

JSX (JavaScript XML) is a syntax extension for JavaScript that allows writing HTML-like code within JavaScript files. It is used in React to define UI components in a more readable and declarative way.

Why is JSX used in React?

  1. Improves Readability – JSX makes UI code more intuitive by combining HTML-like syntax with JavaScript logic.
  2. Enhances Developer Productivity – Writing UI components using JSX is more concise and maintainable compared to using React.createElement().
  3. Babel Compilation – JSX is not native JavaScript; it is transpiled by Babel into standard JavaScript calls (React.createElement) before being executed in the browser.
  4. Supports JavaScript Expressions – JSX allows embedding JavaScript expressions within {} inside HTML-like structures (e.g., {user.name} inside a <h1> tag).
  5. Ensures Security – JSX prevents Cross-Site Scripting (XSS) attacks by automatically escaping values before rendering.
  6. Works with Virtual DOM – JSX makes it easier to define React components, which React efficiently updates using the Virtual DOM.

Example of JSX

 

const Greeting = ({ name }) => {
return <h1>Hello, {name}!</h1>;
};

 

This gets converted to:

 

const Greeting = ({ name }) => {
return React.createElement('h1', null, `Hello, ${name}!`);
};

 

In short, JSX simplifies UI development in React, making code more readable, maintainable, and efficient.

4.

Explain the Virtual DOM and how it improves performance.

Answer

Virtual DOM and How React Fiber Improves Performance

The Virtual DOM (VDOM) is a lightweight, in-memory representation of the real DOM. React uses it to efficiently update and render UI components without directly modifying the actual DOM, which can be slow and resource-intensive.

How the Virtual DOM Works

  1. Initial Render – React creates a Virtual DOM tree that mirrors the real DOM.
  2. State/Props Update – When data changes, React generates a new Virtual DOM tree.
  3. Diffing Algorithm – React compares (diffs) the new Virtual DOM with the previous one to detect changes.
  4. Efficient Updates – Only the modified elements (or their parts) are updated in the real DOM using a process called reconciliation.

How React Fiber Enhances Performance

React introduced Fiber architecture in React 16 to further optimize rendering. Unlike the previous synchronous reconciliation process, Fiber:

  • Enables Incremental Rendering – React splits updates into small tasks, allowing it to pause, prioritize, or resume rendering.
  • Prioritizes UI Updates – User interactions (e.g., animations, input events) are handled before less critical background updates.
  • Supports Concurrent Mode – Future improvements in React, such as concurrent rendering, leverage Fiber to enhance responsiveness.

Why It Improves Performance

  • Minimizes Direct DOM Manipulation – The real DOM is slow because updates trigger reflows and repaints; the Virtual DOM reduces these costly operations.
  • Batch Updates – React batches multiple updates together instead of rendering each change separately.
  • Optimized Rendering with Fiber – Instead of blocking execution, Fiber breaks work into chunks that React processes efficiently.

Example:

  • Without the Virtual DOM: Every update modifies the real DOM, causing multiple reflows and affecting performance.
  • With the Virtual DOM and Fiber: React updates only the necessary elements, making UI updates faster and smoother.

Conclusion

The Virtual DOM, combined with React Fiber, significantly improves performance by reducing unnecessary updates, prioritizing rendering, and enabling smoother UI interactions. This makes React applications more efficient and responsive, especially for complex UIs.

5.

What is the difference between functional components and class components?

Answer

React provides two types of components: functional components and class components.

1. Functional Components

Functional components are JavaScript functions that accept props and return JSX. They are simpler and used primarily for UI rendering.

 

const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;

 

Key Features:

  • Stateless (before Hooks) – Originally, they could not manage state but gained stateful behavior with Hooks (useState, useEffect).
  • Simpler Syntax – More concise and readable.
  • Better Performance – No need to instantiate a class, making them more efficient.
  • Encouraged in Modern React – Since React 16.8, Hooks have made functional components the preferred approach.

 

2. Class Components

 

Class components use ES6 classes and extend React.Component.

class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

 

Key Features:

  • Stateful – Manages local state with this.state and updates it using  this.setState().
  • Lifecycle Methods – Uses methods like componentDidMount(),                                           componentDidUpdate(), and componentWillUnmount().
  • More Boilerplate – Requires explicit constructor, this bindings, and method definitions.

Key Differences

 

Feature Functional Components Class Components
Syntax Function-based ES6 class-based
State useState Hook this.state
Lifecycle useEffect Hook Lifecycle methods (componentDidMount, etc.)
Performance More optimized Slightly slower
Code Complexity Simpler More boilerplate

 

Which One to Use?

  • Use functional components for modern React development as they are lighter, easier to maintain, and fully support Hooks.
  • Class components are still supported but considered legacy after React 16.8.

Conclusion: Functional components are now the preferred way to build React applications due to Hooks, better performance, and simpler code structure.

6.

What are props in React? How do they differ from state? Can We Modify Props in React? What Happens If We Try?

Answer

Props in React

Props (short for “properties”) in React are used to pass data from a parent component to a child component. They are read-only and help make components reusable by allowing dynamic content.

Example of Props

 

const Greeting = ({ name }) => <h1>Hello, {name}!</h1>;

// Usage
<Greeting name="John" />

 

Here, name is a prop passed from the parent component.

How Props Differ from State

 

Feature Props State
Definition Data passed from parent to child Data managed within a component
Mutability Immutable (cannot be changed by the component receiving it) Mutable (can be updated using setState or useState)
Usage Used for passing dynamic data Used for component behavior and interactivity
Who Controls It? Controlled by the parent component Controlled by the component itself

 

Example of State

 

import React, { useState } from 'react';

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

const increment = () => {
setCount(prevCount => prevCount + 1);
};

return (
<button onClick={increment}>
Count: {count}
</button>
);
};

export default Counter;

 

Here, count is a state variable that updates when the button is clicked.

Key Takeaway

  • Use props when passing data between components.
  • Use state when managing component behavior internally.

In short, props allow communication between components, while state enables dynamic updates within a component.

Can We Modify Props in React? What Happens If We Try?

No, props are immutable. If you try to modify them inside a component, React will throw an error.

Example of Incorrect Prop Modification:

 

const Greeting = ({ name }) => {
name = "Bob"; // ❌ Modifying props is not allowed
return <h2>Hello, {name}!</h2>;
};

 

This will cause unintended behavior or errors because props are meant to be controlled by the parent component.

How to Modify Data Passed as Props?

If you need to modify a value inside a child component, you should:

  • Use State inside the child component.
  • Pass a Function as a Prop to update state in the parent.

Example: Updating Parent State from Child Using Props

 

const Parent = () => {
const [name, setName] = useState("Alice");

return <Child name={name} changeName={() => setName("Bob")} />;
};

const Child = ({ name, changeName }) => (
<div>
<h2>Hello, {name}!</h2>
<button onClick={changeName}>Change Name</button>
</div>
);

 

Here, the Child component doesn’t modify props directly but calls a function passed by the parent.

 

Conclusion

  • Props are immutable and passed from parent to child.
  • State is mutable and managed within a component.
  • You cannot modify props directly inside a child component.
  • To modify passed data, use state in the parent and pass an update function as a prop.
7.

How does React handle state management within a component?

Answer

React manages component state using the useState Hook (for functional components) and this.state (for class components). The state represents data that can change over time and triggers a re-render when updated.

1. State in Functional Components (useState Hook)

The useState Hook allows functional components to maintain local state.

 

import { useState } from "react";

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

return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
};

 

  • useState(0) initializes count with 0.
  • setCount(count + 1) updates the state and re-renders the component.

2. State in Class Components

Class components use this.state and this.setState().

 

class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}

increment = () => this.setState({ count: this.state.count + 1 });

render() {
return <button onClick={this.increment}>Count: {this.state.count}</button>;
}
}
  • this.state = { count: 0 } initializes state.
  • this.setState({ count: this.state.count + 1 }) updates state.

 

React’s State Update Mechanism

  • State updates are asynchronous – React batches multiple updates for performance.
  • Updating state triggers a re-render – React compares the Virtual DOM and updates only changed parts.
  • State should not be mutated directly – Always use setState or useState to update values.

When to Use State Management Libraries?

For global state management, React provides:

  • Context API – For prop drilling elimination.
  • Redux / Zustand / Recoil – For complex application-wide state management.

 

Conclusion

React manages state efficiently using useState in functional components and                   this.state in class components. For larger applications, state management solutions like Context API or Redux may be needed.

8.

What is the useState Hook? Can it be used in class components?

Answer

The useState Hook is a built-in React function that allows functional components to manage local state. It was introduced in React 16.8 to replace the need for class components in stateful logic.

How useState Works

It returns an array with two values:

  1. Th current state value
  2. A function to update the state

Example of useState in a Functional Component

 

import { useState } from "react";

const Counter = () => {
const [count, setCount] = useState(0); // Initial state = 0

return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
};
  • count is the state variable.
  • setCount updates the state and triggers a re-render.

 

Can useState Be Used in Class Components?

No, useState cannot be used in class components. Class components use this.state and this.setState() instead.

State in Class Components (Alternative to useState) Class components and their state management are considered legacy in modern React development. Hooks (useState,              useReducer, etc.) are the preferred way to manage state in functional components. However, understanding class-based state is useful for maintaining older React codebases.

 

class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}

increment = () => this.setState({ count: this.state.count + 1 });

render() {
return <button onClick={this.increment}>Count: {this.state.count}</button>;
}
}

 

Key Differences

 

Feature useState (Functional) this.state (Class)
Usage Inside functional components Inside class components
Syntax const [state, setState] = useState(initialValue) this.state = { key: value } + this.setState()
Reusability Easier with Hooks Requires lifecycle methods

 

Conclusion

  • useState only works in functional components.
  • Class components use this.state and this.setState() instead.
  • Hooks (like useState) make functional components the preferred approach in modern React development.
9.

What are controlled and uncontrolled components in React?

Answer

In React, form components (like <input>, <textarea>, <select>) can be controlled or uncontrolled based on how they manage their value.

1. Controlled Components

A controlled component is a form element whose value is managed by React state.

  • The component’s state determines the input value.
  • Updates happen via the onChange event.

Example of a Controlled Component

 

const ControlledInput = () => {
const [text, setText] = React.useState("");

return (
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
);
};

 

Advantages:

  • Predictable state management.
  • Synchronizes UI with state.
  • Easier validation and form handling.

2. Uncontrolled Components

An uncontrolled component manages its own state using the DOM, without React state.

  • React does not control the value.
  • Uses a ref to access the current value.

 

Example of an Uncontrolled Component

 

const UncontrolledInput = () => {
const inputRef = React.useRef(null);

const handleSubmit = () => {
alert(inputRef.current.value);
};

return (
<>
<input type="text" ref={inputRef} />
<button onClick={handleSubmit}>Submit</button>
</>
);
};

 

Advantages:

  • Useful for integrating with non-React code.
  • Can handle large forms efficiently without frequent re-renders.

Key Differences

 

Feature Controlled Components Uncontrolled Components
Value Managed By React state (useState) DOM (useRef)
Update Method onChange handler Direct DOM access
Use Case Dynamic forms, validation Integrating with third-party libraries, performance-sensitive forms
Recommended? Preferred for most cases Use when necessary

 

Conclusion

  • Controlled components are recommended for most React applications as they ensure better control and synchronization.
  • Uncontrolled components are useful when integrating with non-React libraries or for simple, uncontrolled user inputs.
10.

How can you pass data between components in React?

Answer

In React, data is typically passed between components using props, context, or state management libraries.

1. Passing Data from Parent to Child (Props)

Props (properties) allow unidirectional data flow from parent to child components.

 

const Child = ({ message }) => <h1>{message}</h1>;

const Parent = () => <Child message="Hello from Parent" />;

 

Best for: Static or read-only data transfer.

 

2. Passing Data from Child to Parent (Callback Functions)

A child component can send data to a parent using callback props.

 

const Child = ({ onMessage }) => {
return <button onClick={() => onMessage("Data from Child")}>Send</button>;
};

const Parent = () => {
const handleMessage = (data) => alert(data);

return <Child onMessage={handleMessage} />;
};

 

Best for: Allowing child components to update parent state.

3. Passing Data Between Sibling Components (Lifting State Up)

React promotes lifting state up by keeping shared state in a common parent.

 

const Parent = () => {
const [data, setData] = React.useState("");

return (
<>
<Child1 sendData={setData} />
<Child2 receivedData={data} />
</>
);
};

const Child1 = ({ sendData }) => (
<button onClick={() => sendData("Hello from Child1")}>Send</button>
);

const Child2 = ({ receivedData }) => <h1>{receivedData}</h1>;

 

Best for: Sharing state between sibling components.

4. Using Context API for Global State

Context API helps avoid prop drilling by sharing data across multiple components.

 

const DataContext = React.createContext();

const Parent = () => {
return (
<DataContext.Provider value="Shared Data">
<Child />
</DataContext.Provider>
);
};

const Child = () => {
const data = React.useContext(DataContext);
return <h1>{data}</h1>;
};

 

Best for: Global state or deeply nested components.

5. Using State Management Libraries (Redux, Recoil, Zustand)

For complex applications, Redux, Recoil, or Zustand can manage global state efficiently.

 

// Redux example
import { useSelector, useDispatch } from "react-redux";

const data = useSelector((state) => state.value);
const dispatch = useDispatch();
dispatch({ type: "UPDATE", payload: "New Data" });

 

Best for: Large-scale applications with shared state.

 

Conclusion

  • Use props for parent-to-child communication.
  • Use callback functions for child-to-parent communication.
  • Use lifting state up for sibling communication.
  • Use Context API or Redux for global state.

Each method depends on the complexity and needs of the application.

11.

What is the purpose of the useEffect Hook, and how does it work?

Answer

The useEffect Hook in React is used to handle side effects in functional components, such as data fetching, DOM manipulation, event listeners, or subscriptions. It replaces lifecycle methods like componentDidMount, componentDidUpdate, and

componentWillUnmount in class components.

 

Basic Syntax

 

import { useEffect, useState } from "react";

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

useEffect(() => {
console.log(`Count updated: ${count}`);
}, [count]); // Runs when 'count' changes

return <button onClick={() => setCount(count + 1)}>Click Me</button>;
};

 

How useEffect Works

  1. Runs after render – By default, useEffect runs after the component renders.
  2. Dependency Array – Controls when the effect runs:
    • [] (empty array) → Runs once (like componentDidMount).
    • [dependencies] → Runs when dependencies change                        (like componentDidUpdate).
    • No array → Runs on every render.
  3. Cleanup Function – Prevents memory leaks by cleaning up effects before re-running.

Common Use Cases for Cleanup Functions

 

Example with Cleanup (Event Listener Removal)

 

useEffect(() => {
const handleResize = () => console.log("Window resized");
window.addEventListener("resize", handleResize);

return () => {
window.removeEventListener("resize", handleResize);
}; // Cleanup when the component unmounts
}, []);

 

Example with Cleanup (Removing Event Listeners)

 

useEffect(() => {
const handleResize = () => console.log("Window resized");
window.addEventListener("resize", handleResize);

return () => {
window.removeEventListener("resize", handleResize); // Cleanup
};
}, []);

 

Prevents multiple event listeners from stacking up when the component re-renders.

 

Example with Cleanup (Clearing Intervals or Timeouts)

 

useEffect(() => {
const interval = setInterval(() => console.log("Fetching data..."), 1000);

return () => {
clearInterval(interval); // Cleanup
};
}, []);

 

Ensures the interval stops running when the component unmounts.

 

Example with Cleanup (Aborting Fetch Requests). To prevent state updates on unmounted components

 

useEffect(() => {
const controller = new AbortController();
fetch("https://api.example.com/data", { signal: controller.signal });

return () => controller.abort(); // Cleanup
}, []);

 

Prevents fetching data for an unmounted component.

Common Use Cases

  • Fetching data from an API
  • Listening to events (resize, scroll, keypress, etc.)
  • Setting up subscriptions
  • Manipulating the DOM

 

Conclusion

useEffect allows functional components to handle side effects efficiently, making React applications more interactive and dynamic.

12.

What is the use of the key prop in React lists?

Answer

The key prop in React is used to uniquely identify list items when rendering dynamic lists. It helps React track changes efficiently and improve performance by minimizing unnecessary re-renders.

 

Why is the key Prop Important?

  1. Optimizes Rendering – Helps React efficiently update or re-order list items without re-rendering the entire list.
  2. Prevents UI Bugs – Without keys, React may reuse incorrect elements, causing unexpected behavior.
  3. Improves Performance – React uses reconciliation (diffing algorithm) to compare new and previous states and update only changed items.

 

Example of Using the key Prop

 

const List = () => {
const items = ["Apple", "Banana", "Cherry"];

return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li> // Not recommended
))}
</ul>
);
};

 

Avoid using index as a key unless items are static, as it may cause issues when list order changes.

 

Best Practice: Use a Unique Identifier

 

const List = () => {
const items = [
{ id: 1, name: "Apple" },
{ id: 2, name: "Banana" },
{ id: 3, name: "Cherry" },
];

return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li> // Best practice
))}
</ul>
);
};

 

Conclusion

The key prop helps React efficiently update lists by tracking individual items, reducing re-renders, and preventing UI inconsistencies. Always use a unique ID instead of the array index for dynamic lists.

13.

How do you handle forms in React?

Answer

Forms in React are handled using controlled or uncontrolled components, with controlled components being the preferred approach.

1. Controlled Components (Recommended)

In controlled components, the form input’s value is managed by React state, ensuring synchronization between the UI and state.

 

import { useState } from "react";

const Form = () => {
const [name, setName] = useState("");

const handleSubmit = (e) => {
e.preventDefault();
alert(`Submitted: ${name}`);
};

return (
<form onSubmit={handleSubmit}>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit">Submit</button>
</form>
);
};

 

Benefits:

  • Full control over input values.
  • Easier validation and manipulation.

 

2. Uncontrolled Components (Using Refs)

Uncontrolled components store values directly in the DOM instead of React state, accessed via useRef.

 

import { useRef } from "react";

const Form = () => {
const inputRef = useRef(null);

const handleSubmit = (e) => {
e.preventDefault();
alert(`Submitted: ${inputRef.current.value}`);
};

return (
<form onSubmit={handleSubmit}>
<input type="text" ref={inputRef} />
<button type="submit">Submit</button>
</form>
);
};

 

Best for: Integrating with non-React libraries or handling file inputs.

 

3. Handling Multiple Inputs

For forms with multiple fields, use a single state object.

 

const [formData, setFormData] = useState({ name: "", email: "" });

const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};

 

4. Form Validation Example

 

const handleSubmit = (e) => {
e.preventDefault();
if (!name.trim()) alert("Name is required");
};

 

Conclusion

  • Use controlled components for better control and validation.
  • Use uncontrolled components when direct DOM access is needed.
  • Use state objects for handling multiple fields efficiently.

Controlled components are the preferred way to manage forms in React due to their predictability and flexibility.

14.

What is React Router, and why is it used?

Answer

React Router is a popular routing library for React applications that enables navigation between different views (pages) without a full page reload. It helps manage client-side routing in Single Page Applications (SPAs).

Why is React Router Used?

  1. Enables Client-Side Routing – Allows navigation between components without refreshing the page.
  2. Improves Performance – Only updates the necessary components instead of reloading the entire app.
  3. Supports Dynamic Routing – Routes can be parameterized (/user/:id) for dynamic content.
  4. Nested Routing – Supports defining child routes within parent routes.
  5. Easy Navigation Management – Provides Link and NavLink components for declarative navigation.

Basic Example of React Router

Install React Router:

 

npm install react-router-dom

 

Set up routes in App.js:

 

import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";

const Home = () => <h1>Home Page</h1>;
const About = () => <h1>About Page</h1>;

const App = () => {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
};

export default App;

 

Key Components of React Router

  • BrowserRouter – Wraps the app and enables routing.
  • Routes – Defines multiple Route components.
  • Route – Maps URLs to specific components.
  • Link / NavLink – Enables navigation without reloading the page.
  • useParams – Access dynamic route parameters (/user/:id).
  • useNavigate – Programmatic navigation to other routes.

Conclusion

React Router is essential for building multi-page SPAs by enabling seamless navigation, improving performance, and allowing dynamic route handling.

15.

What are higher-order components (HOCs) in React?

Answer

A Higher-Order Component (HOC) is a function that takes a component as an argument and returns a new enhanced component. HOCs enable code reusability, logic sharing, and component composition in React.

Why Use HOCs?

  1. Code Reusability – Share logic across multiple components.
  2. Separation of Concerns – Keep business logic separate from UI components.
  3. Enhance Components – Add additional functionality (e.g., authentication, logging, API handling).

 

Basic Example of a HOC

This HOC adds a loading state to any component.

 

const withLoading = (WrappedComponent) => {
return (props) => props.isLoading ? <p>Loading...</p> : <WrappedComponent {...props} />;
};

// Usage
const UserList = ({ users }) => (
<ul>{users.map((user) => <li key={user.id}>{user.name}</li>)}</ul>
);

const EnhancedUserList = withLoading(UserList);

// In App
<EnhancedUserList isLoading={false} users={[{ id: 1, name: "John" }]} />;

 

UserList now automatically handles a loading state!

 

Common Use Cases for HOCs

  • Authentication Handling – Restrict access to authenticated users.
  • Logging & Analytics – Track user interactions.
  • Data Fetching – Handle API calls and inject data into components.
  • Theming – Apply different styles dynamically.

Key Considerations

  • Avoid Overuse – HOCs can lead to deeply nested component trees (wrapper hell).
  • Use Hooks Instead – Many use cases of HOCs are now handled with custom Hooks             (useContext, useReducer).

Conclusion

Higher-Order Components enhance reusability by wrapping components with additional logic, but modern React often prefers Hooks and Context API for similar use cases.

16.

What is the difference between useEffect and lifecycle methods in class components?

Answer

In class components, React uses lifecycle methods (componentDidMount,

componentDidUpdate, componentWillUnmount) to handle side effects. In functional components, the useEffect Hook replaces these lifecycle methods.

 

1. Lifecycle Methods in Class Components

Class components use separate methods for different lifecycle stages:

 

class Example extends React.Component {
componentDidMount() {
console.log("Component mounted");
}

componentDidUpdate() {
console.log("Component updated");
}

componentWillUnmount() {
console.log("Component will unmount");
}

render() {
return <h1>Hello</h1>;
}
}

 

Downside: Requires multiple methods to handle different lifecycle stages.

 

2. useEffect in Functional Components

The useEffect Hook combines all lifecycle methods into one function.

 

import { useEffect } from "react";

const Example = () => {
useEffect(() => {
console.log("Component mounted or updated");

return () => console.log("Component will unmount"); // Cleanup function
}, []); // Dependency array
return <h1>Hello</h1>;
};

 

Simplifies logic by merging multiple lifecycle behaviors.

 

Key Differences

 

Feature Lifecycle Methods (Class) useEffect (Functional)
Component Mounting componentDidMount useEffect(() => {...}, [])
Component Updating componentDidUpdate useEffect(() => {...}, [dependencies])
Component Unmounting componentWillUnmount useEffect(() => {... return cleanup }, [])
Code Complexity Separate methods for each phase One function handles multiple phases
Performance Requires class instantiation Lightweight and optimized

 

Conclusion

  • Class components use lifecycle methods, while functional components use                                                                               useEffect.
  • useEffect simplifies side effect management by merging multiple lifecycle stages.
  • Modern React prefers functional components with Hooks over class components.
17.

Explain how React Context API works and when to use it over props.

Answer

he React Context API is a built-in state management solution that allows data to be shared across components without prop drilling. It is useful for global state like themes, authentication, and user preferences.

 

How React Context API Works

  1. Create a Context – Defines a global store.
  2. Provide a Context – Wrap components with a Provider to pass data.
  3. Consume the Context – Use useContext to access data inside any component.

 

Example of React Context API

 

import React, { createContext, useContext, useState } from "react";

// Step 1: Create Context
const ThemeContext = createContext();

// Step 2: Provide Context
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};

// Step 3: Consume Context
const ThemedComponent = () => {
const { theme, setTheme } = useContext(ThemeContext);
return (
<>
<p>Current Theme: {theme}</p>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Toggle Theme
</button>
</>
);
};

// Usage
const App = () => (
<ThemeProvider>
<ThemedComponent />
</ThemeProvider>
);

 

When to Use Context API Over Props?

 

Scenario Props Context API
Simple Parent-Child Communication yes no
Avoid Prop Drilling (Deep Nesting) no yes
Global State (Auth, Theme, User Data) yes yes
Reusable Components (Without State Dependency) no no

 

Props are preferred for local state and direct communication.

Context API is best for global state management and eliminating prop drilling.

 

Conclusion

React Context API improves state management by providing a direct way to share data between components without passing props manually. It should be used when multiple components need access to the same data, but for complex applications, Redux or Zustand may be better choices.

18.

What are React Portals, and when should you use them?

Answer

React Portals allow rendering a component outside of its parent component’s DOM hierarchy while keeping it within the React tree. This is useful when you need to bypass CSS overflow or stacking context issues.

 

How React Portals Work

  1. Create a Portal container in index.html:

 

<div id="portal-root"></div>

2. Use ReactDOM.createPortal() to render the component:

 

import ReactDOM from "react-dom";

const Modal = ({ children }) => {
return ReactDOM.createPortal(
<div className="modal">{children}</div>,
document.getElementById("portal-root")
);
};

const App = () => (
<>
<h1>Main App</h1>
<Modal>Portal Content</Modal>
</>
);

 

When Should You Use React Portals?

  • Modals & Dialogs – Ensures the modal appears above everything else, bypassing parent styles.
  • Tooltips & Popovers – Prevents unwanted clipping inside containers with overflow: hidden.
  • Global Notifications – Displays alerts or toast messages without affecting existing layout structure.

Conclusion

React Portals help render components outside their parent DOM hierarchy while maintaining React’s state and event bubbling. They are essential for modals, popups, tooltips, and overlays that need to break out of the normal component tree.

19.

How does React handle reconciliation and rendering optimization?

Answer

React reconciliation is the process of efficiently updating the DOM when component state or props change. It uses a diffing algorithm and Virtual DOM to optimize rendering.

 

1. How React Reconciliation Works

  1. Virtual DOM Update – React creates a new Virtual DOM tree when state or props change.
  2. Diffing Algorithm – React compares the new Virtual DOM with the previous one to identify changes.
  3. Efficient Updates (Reconciliation) – Only the changed elements are updated in the real DOM.

Example of efficient updates:

 

const [count, setCount] = useState(0);
return <h1>Count: {count}</h1>;

 

When setCount(1) runs, React only updates the <h1> instead of re-rendering the entire page.

2. Rendering Optimization Techniques

  • Using key in Lists – Helps React track and update list items efficiently.

 

items.map((item) => <li key={item.id}>{item.name}</li>);

 

  • Memoization (React.memo) – Prevents unnecessary re-renders of functional components.

 

const MemoizedComponent = React.memo(MyComponent);

 

  • Using useCallback and useMemo – Optimizes function and computed values.

 

const memoizedValue = useMemo(() => computeExpensiveValue(data), [data]);

 

  • Avoiding Unnecessary State Updates – Updating state only when necessary reduces re-renders.

 

if (prevValue !== newValue) setState(newValue);

 

  • Lazy Loading with React.lazy – Loads components only when needed, improving performance.

 

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

 

Conclusion

React optimizes rendering through reconciliation, Virtual DOM, and diffing algorithms. Using best practices like memoization, lazy loading, and efficient state updates improves performance and reduces unnecessary re-renders.

20.

What are the benefits of using memoization (React.memo and

useMemo) in React?

Answer

Memoization in React helps optimize performance by caching the results of expensive computations or preventing unnecessary re-renders. React provides two key tools for memoization:

 

1. React.memo – Optimizing Component Re-Renders

React.memo is a higher-order component (HOC) that prevents a functional component from re-rendering if its props haven’t changed.

Example:

 

const MemoizedComponent = React.memo(({ name }) => {
console.log("Re-rendered!");
return <h1>Hello, {name}!</h1>;
});

 

Prevents unnecessary re-renders when name stays the same.

 

2. useMemo – Optimizing Expensive Calculations

useMemo caches the result of computationally expensive functions to avoid recalculating on every render.

Example:

 

import { useMemo } from "react";

const ExpensiveComponent = ({ numbers }) => {
const sum = useMemo(() => numbers.reduce((a, b) => a + b, 0), [numbers]);

return <h1>Sum: {sum}</h1>;
};

 

Recalculates only when numbers change, improving efficiency.

 

Benefits of Memoization in React

  • Reduces Unnecessary Re-Renders – Improves performance by skipping updates.
  • Optimizes Expensive Calculations – Prevents recalculating the same values repeatedly.
  • Improves UI Responsiveness – Faster rendering leads to a smoother experience.
  • Enhances Large Component Performance – Useful in lists, tables, and complex UIs.

 

When to Use Memoization?

  • Use React.memo for pure functional components that receive the same props frequently.
  • Use useMemo when dealing with costly computations that don’t need to run on every render.

 

Conclusion

Memoization with React.memo and useMemo boosts performance by caching values and preventing redundant renders, making React applications more efficient.

21.

What is useCallback, and when should it be used?

Answer

useCallback is a React Hook that memoizes functions to prevent unnecessary re-creations on every render. It is useful for performance optimization in components that pass functions as props.

 

How useCallback Works

It returns a memoized function that only changes if its dependencies change.

Example Without useCallback (Inefficient)

 

const Parent = () => {
const handleClick = () => console.log("Clicked!");

return <Child onClick={handleClick} />;
};

 

Issue: handleClick is recreated on every render, causing unnecessary re-renders in Child.

 

Optimized with useCallback

 

import { useCallback } from "react";

const Parent = () => {
const handleClick = useCallback(() => console.log("Clicked!"), []);

return <Child onClick={handleClick} />;
};

 

Now handleClick remains the same across renders, preventing unnecessary re-renders of Child.

 

When Should useCallback Be Used?

  • Passing Functions as Props – Prevents child components from re-rendering unnecessarily.
  • Performance Optimization – Useful in components with expensive computations.
  • Avoiding Unnecessary State Updates – Helps with event listeners and handlers.

 

When NOT to Use useCallback?

  • On simple functions that don’t affect performance.
  • If the function is already recreated infrequently (e.g., inside an event handler).

 

Conclusion

useCallback memoizes functions to reduce unnecessary renders, making it useful in performance-critical scenarios, especially when passing callbacks to child components.

22.

Explain lazy loading and how React’s React.lazy and Suspense help improve performance.

Answer

Lazy loading is a technique that delays loading non-essential components until they are needed. This improves performance by reducing the initial load time of an application.

 

How React.lazy Works

React.lazy() allows components to be loaded dynamically only when required.

 

Example of Lazy Loading

 

import React, { lazy, Suspense } from "react";

const LazyComponent = lazy(() => import("./MyComponent"));

const App = () => (
<Suspense fallback={<p>Loading...</p>}>
<LazyComponent />
</Suspense>
);

 

Here’s what happens:

  • LazyComponent is not loaded initially.
  • When needed, React fetches the component asynchronously.
  • Suspense displays a fallback UI (like a loading spinner) until loading is complete.

 

How Suspense Helps

Suspense acts as a wrapper that handles loading states while waiting for the lazy component to load.

  • Improves performance – Loads only what’s needed instead of loading everything upfront.
  • Enhances user experience – Shows a fallback UI while loading content.
  • Optimizes large applications – Reduces JavaScript bundle size, speeding up initial page loads.

 

When to Use Lazy Loading?

  • Large components like modals, charts, or dashboards.
  • Routes in a single-page application (SPA).
  • Third-party libraries that aren’t needed immediately.

 

Conclusion

Lazy loading with React.lazy and Suspense boosts performance by reducing initial bundle size and ensuring components are loaded only when necessary.

23.

How do you optimize a React application for performance?

Answer

Optimizing a React application involves reducing unnecessary renders, minimizing JavaScript execution, and improving load times. Key strategies include:

 

1. Optimize Rendering

Use React.memo to Prevent Unnecessary Re-renders

 

const MemoizedComponent = React.memo(MyComponent);

 

Use useCallback and useMemo for Function and Value Memoization

 

const memoizedValue = useMemo(() => computeExpensiveValue(data), [data]);
const memoizedFunction = useCallback(() => doSomething(), [dependencies]);

 

2. Optimize Component Structure

Split Code with Lazy Loading (React.lazy & Suspense)

 

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

 

Keep Component State Local – Move unnecessary state out of high-level components to avoid re-renders.

 

3. Optimize Lists and Rendering

Use key Props Efficiently in Lists

 

items.map((item) => <li key={item.id}>{item.name}</li>);

 

Avoid Inline Functions and Objects in JSX

 

// ❌ Creates a new function on every render
<button onClick={() => doSomething()} />

// ✅ Use useCallback
const handleClick = useCallback(() => doSomething(), []);
<button onClick={handleClick} />

 

Reduce JavaScript Bundle Size

Remove Unused Dependencies – Analyze bundle size with tools like webpack-bundle-analyzer.

Use Production Build – Enable optimizations with:

 

npm run build

 

Use Tree Shaking – Import only needed functions.

 

// Imports everything
import * as lodash from "lodash";

// Imports only what’s needed
import { debounce } from "lodash";

 

5. Optimize API Calls and State Management

Use Pagination or Infinite Scrolling for Large Data Sets

Use Context API, Redux, or Zustand for Efficient State Management

Debounce Expensive Calls (e.g., Search Inputs)

 

const debouncedSearch = useCallback(debounce((query) => fetchData(query), 500), []);

 

Conclusion

Optimizing a React app involves reducing unnecessary renders, using memoization, optimizing lists, and minimizing bundle size to improve performance and user experience.

24.

How does server-side rendering (SSR) work in React, and what are its advantages?

Answer

Server-Side Rendering (SSR) in React is a technique where a React application is rendered on the server instead of the client’s browser. This improves performance and SEO by sending a fully rendered HTML page to the client.

How SSR Works in React

  1. Client Request – The browser requests a React page.
  2. Server Renders the React Component – The server executes React code and generates an HTML response.
  3. Pre-rendered HTML Sent to Client – The user sees the initial content immediately.
  4. Hydration – React reattaches event listeners, making the app interactive.

Example of SSR Using Next.js (React Framework)

 

export async function getServerSideProps() {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
return { props: { data } };
}

const Page = ({ data }) => <h1>{data.title}</h1>;

export default Page;

 

getServerSideProps fetches data on the server before rendering the page.

 

Advantages of SSR

  • Improved SEO – Search engines can index pre-rendered content, boosting visibility.
  • Faster First Paint – Users see content sooner since the HTML is ready.
  • Better Performance for Low-Powered Devices – Reduces client-side computation.
  • Consistent Content – Ensures data consistency before hydration.

 

When to Use SSR?

  • When SEO is crucial (e.g., blogs, e-commerce).
  • When fast initial load times are needed.
  • When pre-fetching dynamic data before rendering.

 

Conclusion

SSR renders React pages on the server, improving SEO, performance, and user experience. It’s commonly used with Next.js for dynamic, optimized applications.

25.

What are the advantages and disadvantages of Redux compared to Context API?

Answer

Redux and Context API are both used for state management in React, but they serve different purposes and have their own pros and cons.

 

Advantages of Redux

  • Predictable State Management – Uses a single source of truth (store) and follows strict rules (reducers, actions).
  • Powerful Debugging Tools – Redux DevTools allow time-travel debugging.
  • Scalability – Works well for large applications with complex state.
  • Middleware Support – Integrates with Redux Thunk or Redux Saga for handling async actions (e.g., API calls).

 

Disadvantages of Redux

  • Boilerplate Code – Requires actions, reducers, and dispatch functions, making it verbose.
  • Complexity for Small Apps – Overkill for simple state management.
  • Performance Overhead – Frequent re-renders if not optimized properly.

 

 

Advantages of Context API

  • Built-In to React – No need for external libraries.
  • Simpler State Management – Easier to use for small-to-medium applications.
  • No Extra Boilerplate – Uses useContext with useState or useReducer.
  • Better Performance for Localized State – Efficient when state is needed in only a few components.

 

Disadvantages of Context API

  • Performance Issues for Frequent Updates – If the context value changes frequently, all consuming components re-render.
  • No Middleware Support – Lacks built-in async handling like Redux middleware.
  • Not Ideal for Large-Scale State Management – Becomes harder to manage in complex applications.

 

When to Use Redux vs. Context API?

 

Feature Redux Context API
App Size Large apps Small-to-medium apps
State Complexity Complex state (e.g., authentication, API caching) Simple state (e.g., theme, user preferences)
Performance Optimization Better with selectors & memoization Can cause unnecessary re-renders
Middleware Support Supports async middleware (Thunk, Saga) No built-in middleware
Ease of Use More boilerplate, but powerful Simpler, but less scalable

 

Conclusion

  • Use Redux for large-scale, complex applications requiring centralized state management and middleware.
  • Use Context API for simpler, lightweight state sharing without external dependencies.
  • For performance-sensitive applications, consider React Query, Zustand, or Recoil as alternatives.
26.

What are the main differences between Redux Toolkit and traditional Redux?

Answer

Redux Toolkit (RTK) is the official, recommended way to use Redux. It simplifies state management by reducing boilerplate and improving efficiency compared to traditional Redux.

 

Feature Traditional Redux Redux Toolkit (RTK)
Boilerplate Code Requires actions, reducers, and constants separately Uses createSlice() to reduce boilerplate
State Mutability Requires immutable updates with spread operators Uses Immer.js, allowing direct state mutation
Setup Complexity Manual store configuration (createStore) Pre-configured with configureStore()
Thunk Middleware Must install and configure manually Built-in support for async actions (createAsyncThunk)
Best Practices Requires manual structuring Enforces Redux best practices automatically
Developer Experience More code, prone to complexity Simpler syntax, easier debugging

 

Example: Traditional Redux vs. Redux Toolkit

1. Traditional Redux Setup

 

// Actions
const increment = () => ({ type: "INCREMENT" });

// Reducer
const counterReducer = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
default:
return state;
}
};

// Store
const store = createStore(counterReducer);

 

2. Redux Toolkit Setup (Simpler & Cleaner)

 

import { createSlice, configureStore } from "@reduxjs/toolkit";

const counterSlice = createSlice({
name: "counter",
initialState: 0,
reducers: {
increment: (state) => state + 1,
},
});

const store = configureStore({ reducer: counterSlice.reducer });

export const { increment } = counterSlice.actions;

 

Less code, better readability, and built-in best practices!

 

 

When to Use Redux Toolkit Over Traditional Redux?

  • For new projects, always use RTK—it’s more efficient and less error-prone.
  • For existing Redux apps, migrating to RTK improves maintainability and simplifies code.
  • For complex state logic, RTK’s built-in tools (like createAsyncThunk) handle async operations more effectively.

 

Conclusion

Redux Toolkit eliminates unnecessary boilerplate, simplifies state management, and follows best practices. It is now the default and recommended approach for using Redux.

27.

How would you implement global state management in a React application?

Answer

Global state management in React is used when multiple components need to share and update the same state. There are several approaches, depending on the complexity of the application.

 

1. Using React Context API (Best for Small to Medium Apps)

React’s built-in Context API allows global state sharing without prop drilling.

 

import { createContext, useContext, useState } from "react";

// Create Context
const GlobalContext = createContext();

// Provider Component
const GlobalProvider = ({ children }) => {
const [theme, setTheme] = useState("light");

 

Best for: Small apps, themes, authentication, or user preferences.

 

 

2. Using Redux Toolkit (Best for Large-Scale Apps)

For complex state (e.g., API data, authentication), Redux Toolkit simplifies global state management

 

import { createSlice, configureStore } from "@reduxjs/toolkit";
import { Provider, useSelector, useDispatch } from "react-redux";

// Create Slice
const counterSlice = createSlice({
name: "counter",
initialState: 0,
reducers: {
increment: (state) => state + 1,
},
});

 

Best for: Large apps needing predictable, centralized state management.

 

 

3. Using Zustand (Simpler Alternative to Redux)

Zustand provides lightweight global state without Context API overhead.

 

import create from "zustand";

const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));

const Counter = () => {
const { count, increment } = useStore();
return <button onClick={increment}>Count: {count}</button>;
};

 

Best for: Simple, scalable global state management with minimal boilerplate.

Choosing the Right Approach

 

Approach Best For
Context API Small-to-medium apps, simple state sharing
Redux Toolkit Large-scale apps, complex state management
Zustand Lightweight, scalable state management

 

Conclusion

To implement global state in React:

  • Use Context API for simple state sharing.
  • Use Redux Toolkit for large-scale applications.
  • Use Zustand for an efficient, minimal setup.

Choose the right tool based on your app size and complexity.

28.

What is the purpose of useReducer, and how is it different from

useState?

Answer

The useReducer Hook is used for managing complex state logic in React components. It is an alternative to useState, especially when state updates involve multiple conditions or actions.How useReducer Works

 

 

useReducer takes two arguments:

  1. A reducer function – Defines how state updates based on actions.
  2. An initial state – The starting value of the state.

 

import { useReducer } from "react";

// Reducer function
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
};

// Component using useReducer
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });

return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
};

 

Here’s what happens:

  • dispatch({ type: "increment" }) triggers the reducer function.
  • The reducer updates the state based on the action type.

 

Differences Between useReducer and useState

 

Feature useState useReducer
Best For Simple state (e.g., toggles, form inputs) Complex state (e.g., multiple state changes, logic-heavy updates)
State Update Directly modifies state (setState) Uses an action-based approach (dispatch)
Performance Simpler, but can cause frequent re-renders Optimized for managing complex logic efficiently
Scalability Best for local component state Better for larger, structured state updates

 

When to Use useReducer?

  • When state logic depends on previous state values (e.g., counters, form validation).
  • When multiple actions modify state (e.g., handling API responses).
  • When using useState becomes hard to manage (e.g., nested state objects).

 

Conclusion

  • Use useState for simple, independent state updates.
  • Use useReducer for structured, complex state updates with multiple conditions.
  • For global state, consider Context API + useReducer instead of Redux for lightweight state management.

 

29.

How do you test React components using Jest and React Testing Library?

Answer

Testing React components ensures they work correctly and handle user interactions as expected. Jest is the default testing framework for React, and React Testing Library (RTL) provides utilities for testing UI behavior.

 

1. Setting Up Jest and React Testing Library

 

If not already installed, install react-testing-library and jest-dom:

npm install --save-dev @testing-library/react @testing-library/jest-dom

 

2. Writing a Basic Test for a React Component

Consider a simple Button component:

 

const Button = ({ onClick, label }) => (
<button onClick={onClick}>{label}</button>
);

export default Button;

 

Now, test if the button renders and handles clicks:

 

import { render, screen, fireEvent } from "@testing-library/react";
import Button from "./Button";

test("renders button with correct label", () => {
render(<Button label="Click Me" />);
expect(screen.getByText("Click Me")).toBeInTheDocument();
});

test("calls onClick when button is clicked", () => {
const handleClick = jest.fn();
render(<Button label="Click Me" onClick={handleClick} />);

fireEvent.click(screen.getByText("Click Me"));
expect(handleClick).toHaveBeenCalledTimes(1);
});

 

What This Test Does:

  • Checks if the button renders correctly (getByText).
  • Simulates a click event (fireEvent.click).
  • Verifies if the onClick handler was called (jest.fn()).

 

3. Testing Asynchronous Components

For async operations (e.g., API calls), use findBy:

 

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

test("displays message after async fetch", async () => {
render(<AsyncComponent />);
expect(await screen.findByText("Data Loaded")).toBeInTheDocument();
});

 

4. Snapshot Testing

Snapshot tests ensure UI consistency:

 

import { render } from "@testing-library/react";
import Button from "./Button";

test("matches snapshot", () => {
const { asFragment } = render(<Button label="Click Me" />);
expect(asFragment()).toMatchSnapshot();
});

 

5. Best Practices for Testing React Components

  • Test Behavior, Not Implementation – Focus on UI interactions, not internal logic.
  • Use screen.getBy* Methods – Improves test readability.
  • Mock API Calls – Prevents real network requests during testing.

 

Conclusion

Jest and React Testing Library enable efficient, behavior-driven testing by verifying component rendering, interactions, and async behavior.

30.

What are the best practices for structuring a React.js project?

Answer

A well-structured React project improves maintainability, scalability, and readability. Here are key best practices:

 

1. Follow a Clear Folder Structure

A common structure for medium to large applications:

 

/src
/components → Reusable UI components
/pages → Page-specific components
/hooks → Custom React Hooks
/context → Context API state management
/services → API calls and external integrations
/utils → Helper functions and constants
/assets → Images, fonts, and styles
/tests → Unit and integration tests
/store → Redux or Zustand store (if used)
App.js → Root component
index.js → Entry point

 

2. Keep Components Small and Reusable

  • Break down large components into smaller, single-responsibility components.
  • Store reusable UI components inside /components.

 

// Example: Button.js
const Button = ({ label, onClick }) => (
<button onClick={onClick}>{label}</button>
);
export default Button;

 

3. Use Context API or State Management Libraries Wisely

  • Use useState for local component state.
  • Use Context API for sharing state across multiple components.
  • Use Redux or Zustand for large-scale state management.

 

4. Separate API Calls Into a services/ Folder

 

// services/api.js
export const fetchData = async (url) => {
const response = await fetch(url);
return response.json();
};

 

5. Use Absolute Imports Instead of Relative Imports

Instead of:

 

import Button from "../../../components/Button";

 

Use Absolute Paths (Configure jsconfig.json or tsconfig.json)

 

import Button from "components/Button";

 

6. Use Lazy Loading for Performance Optimization

 

import { lazy, Suspense } from "react";

const LazyComponent = lazy(() => import("./components/LazyComponent"));

<Suspense fallback={<p>Loading...</p>}>
<LazyComponent />
</Suspense>;

 

7. Follow Naming Conventions

  • Components: PascalCase (MyComponent.js)
  • Hooks: camelCase (useAuth.js)
  • Constants: UPPER_CASE (config.js)
  • Files: Meaningful names instead of index.js everywhere.

 

 

8. Write Tests for Components and Functions

  • Unit Tests – For individual components.
  • Integration Tests – For multiple component interactions.
  • End-to-End Tests – For full user flows.

 

import { render, screen } from "@testing-library/react";
import Button from "components/Button";

test("renders button correctly", () => {
render(<Button label="Click Me" />);
expect(screen.getByText("Click Me")).toBeInTheDocument();
});

 

Conclusion

Following these best practices ensures clean, scalable, and maintainable React applications. A well-structured project improves collaboration, debugging, and performance over time.

React.js Interview Questions for Experienced Levels

1.

How does React Fiber improve rendering performance compared to earlier versions?

Answer

React Fiber is a reconciliation algorithm introduced in React 16 to improve rendering performance. It replaces the older stack-based reconciliation with a more efficient incremental rendering system.

Key Performance Improvements in React Fiber

  1. Incremental Rendering (Time-Slicing)
    • Fiber breaks rendering into small units and prioritizes updates, allowing React to pause and resume work efficiently.
    • This prevents UI lag and keeps apps responsive.
  2. Prioritization of Updates
    • React Fiber assigns different priority levels to updates (e.g., user interactions are prioritized over background tasks).
    • This makes animations and input handling smoother.
  3. Concurrency Mode Support
    • Enables non-blocking rendering, allowing React to process multiple updates asynchronously.
  4. Efficient Reconciliation (Diffing Algorithm)
    • Faster re-render detection, reducing unnecessary updates.
    • Uses a linked list structure instead of a recursive call stack for better memory management.
  5. Better Error Handling
    • Introduces error boundaries to gracefully handle component failures without crashing the app.

 

Comparison: React Fiber vs. Older Versions

 

Feature Older React (Stack Reconciliation) React Fiber
Rendering Synchronous (blocks UI) Asynchronous (time-slicing)
Reconciliation Recursive, slower updates Linked-list based, optimized updates
Prioritization No prioritization High-priority updates handled first
Error Handling No built-in error boundaries Supports error boundaries

 

Conclusion

React Fiber optimizes rendering performance by enabling incremental updates, prioritizing UI tasks, supporting concurrency, and improving memory efficiency. This results in smoother, faster, and more responsive React applications.

2.

What are the limitations of the Context API, and when should you prefer Redux, Redux Toolkit or Zustand?

Answer

The Context API is a built-in React feature used for global state management. While useful, it has some limitations that make Redux, Redux Toolkit (RTK), or Zustand better choices in certain cases.

 

Limitations of the Context API

  1. Performance Issues – Every context update triggers a re-render of all consuming components, which can slow down large applications.
  2. Prop Drilling in Large Apps – Although it avoids prop drilling, managing deeply nested state across multiple contexts can become complex.
  3. No Built-in Middleware Support – Unlike Redux and RTK, Context API does not have middlewares for async logic like logging, caching, or API handling.
  4. Harder Debugging – Context API lacks advanced debugging tools like Redux DevTools.
  5. State Splitting Complexity – Managing multiple contexts for different state slices can be tedious and hard to maintain.

 

When to Prefer Redux, Redux Toolkit, or Zustand

 

Use Case Context API Redux Redux Toolkit (RTK) Zustand
Small to Medium Apps ✅ Best Choice ❌ Overkill ❌ Overkill ✅ Good Choice
Complex State Management ❌ Hard to Scale ✅ Handles Large State ✅ Simplifies Redux ✅ Lightweight but Scalable
Performance Optimization ❌ Re-renders on updates ✅ Optimized with selectors ✅ Efficient with createSlice ✅ Optimized with slices
Middleware Support ❌ No built-in support ✅ Supports Thunk/Saga ✅ Built-in async handling (createAsyncThunk) ✅ Supports async logic
Debugging Tools ❌ Limited ✅ Redux DevTools ✅ Redux DevTools + Built-in best practices ✅ Simple but effective

 

Conclusion

  • Use Context API for small apps with simple global state (e.g., themes, authentication).
  • Use Redux for large-scale applications that require strict state management and debugging tools.
  • Use Redux Toolkit (RTK) when you need Redux but with less boilerplate, built-in middleware, and async support.
  • Use Zustand for lightweight, efficient state management without Redux’s complexity.
3.

How does useReducer compare to Redux for state management?

Answer

Both useReducer and Redux help manage complex state in React applications, but they serve different purposes.

useReducer (Local State Management)

useReducer is a built-in React Hook for handling component-level state with an action-based approach.

Example: Counter with useReducer

 

const reducer = (state, action) => {
switch (action.type) {
case "increment": return { count: state.count + 1 };
case "decrement": return { count: state.count - 1 };
default: return state;
}
};

const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });

return <button onClick={() => dispatch({ type: "increment" })}>Count: {state.count}</button>;
};

 

Best For:

  • Component-specific state
  • Simple applications
  • Avoiding unnecessary external dependencies

 

Redux (Global State Management)

Redux manages application-wide state with a centralized store, making it suitable for complex apps.

When to Use Redux Instead of useReducer

 

Feature useReducer Redux
Scope Local component state Global application state
Boilerplate Minimal setup More setup (actions, reducers, store)
Middleware Support ❌ No middleware ✅ Supports Thunk, Saga, etc.
Debugging Tools ❌ Limited ✅ Redux DevTools
Performance Optimization ❌ Can cause unnecessary re-renders ✅ Optimized with selectors

 

Conclusion

  • Use useReducer for local state management in small-to-medium components.
  • Use Redux for large-scale applications needing global state, middleware, and debugging tools.
  • If Redux is overkill but global state is needed, consider Zustand or Context API + useReducer.
4.

What are the best practices for structuring large-scale React applications?

Answer

Best Practices for Structuring Large-Scale React Applications

A well-structured React application improves maintainability, scalability, and performance. Below are key best practices for organizing large-scale projects.

 

 

1. Follow a Scalable Folder Structure

A modular approach keeps components and logic well-organized:

 

/src
/components → Reusable UI components
/pages → Page-specific components
/hooks → Custom Hooks
/context → Context API for global state
/store → Redux/Zustand store (if used)
/services → API calls and external integrations
/utils → Helper functions/constants
/assets → Images, fonts, and styles
/tests → Unit and integration tests
App.js → Root component
index.js → Entry point

 

2. Keep Components Small and Reusable

  • Follow the Single Responsibility Principle (SRP) – Each component should have one clear function.
  • Extract reusable UI elements (buttons, forms) into a /components folder.
// Example: Reusable Button Component
const Button = ({ label, onClick }) => (
<button onClick={onClick}>{label}</button>
);
export default Button;

 

3. Use Centralized State Management Wisely

  • Use useState for local component state.
  • Use Context API for small global states (e.g., theme, authentication).
  • Use Redux/Zustand for large, complex applications.

 

4. Separate Business Logic from UI

  • Keep API calls in a services/ folder.

 

// Example: API Service
export const fetchUsers = async () => {
const response = await fetch("/api/users");
return response.json();
};

 

5. Optimize Performance

  • Use React.memo and useCallback to prevent unnecessary re-renders.
  • Use Lazy Loading (React.lazy) for components that aren’t needed immediately.
  • Use Code Splitting to reduce bundle size.

 

const LazyComponent = React.lazy(() => import("./MyComponent"));
<Suspense fallback={<p>Loading...</p>}>
<LazyComponent />
</Suspense>;

 

6. Use Absolute Imports

Avoid deep relative paths by configuring jsconfig.json:

 

{
"compilerOptions": {
"baseUrl": "src"
}
}

 

Now, instead of:

 

import Button from "../../../components/Button";

 

Use:

 

import Button from "components/Button";

 

7. Write Unit and Integration Tests

  • Use Jest & React Testing Library for testing components.
  • Mock API requests in tests.

 

import { render, screen } from "@testing-library/react";
import Button from "components/Button";

test("renders button correctly", () => {
render(<Button label="Click Me" />);
expect(screen.getByText("Click Me")).toBeInTheDocument();
});

 

Conclusion

By following these best practices—clear folder structure, reusable components, state management, optimized performance, and testing—you can build scalable, maintainable, and efficient React applications.

5.

How does React handle reconciliation, and what are some ways to optimize re-renders?

Answer

Reconciliation is React’s process of updating the DOM efficiently when state or props change. It uses the Virtual DOM and a diffing algorithm to identify what has changed and updates only those parts instead of re-rendering the entire UI.

 

How React Handles Reconciliation

  1. Virtual DOM Comparison – React creates a new Virtual DOM tree and compares it with the previous one.
  2. Diffing Algorithm – React finds differences (changes, additions, or deletions).
  3. Efficient Updates – Only modified elements are updated in the real DOM using a process called reconciliation.

Example: If a component’s state changes, React only updates the affected part instead of reloading the entire page.

 

Ways to Optimize Re-Renders

1. Use React.memo to Prevent Unnecessary Renders

 

const MemoizedComponent = React.memo(({ name }) => <h1>{name}</h1>);

 

Prevents re-renders if name prop hasn’t changed.

2. Use useCallback for Memoizing Functions

 

const handleClick = useCallback(() => console.log("Clicked"), []);

 

Prevents function recreation on every render.

3. Use useMemo for Expensive Computations

 

const calculatedValue = useMemo(() => expensiveFunction(data), [data]);

 

Only recalculates when data changes.

4. Optimize Context API Usage

Avoid unnecessary re-renders by structuring Context providers efficiently or using selectors in global state management in tools like Redux.

5. Use Efficient List Rendering with key Props

Only recalculates when data changes.

4. Optimize Context API Usage

Avoid unnecessary re-renders by structuring Context providers efficiently or using selectors in global state management in tools like Redux.

5. Use Efficient List Rendering with key Props

 

items.map((item) => <li key={item.id}>{item.name}</li>);

 

Helps React track list items efficiently.

6. Split Components to Minimize Updates

Move stateful logic to a separate component so only necessary parts re-render.

 

Conclusion

React optimizes rendering with reconciliation, and performance can be further improved using memoization (React.memo, useMemo, useCallback), optimized list rendering, and proper state management.

6.

What is the difference between React.memo, useMemo, and

useCallback, and when should you use each?

Answer

Difference Between React.memo, useMemo, and useCallback and When to Use Them

All three are performance optimization tools in React, but they serve different purposes.

 

1. React.memo – Prevents Unnecessary Re-Renders of Components

  • React.memo memoizes a functional component and prevents it from re-rendering unless its props change.

Example:

 

const MemoizedComponent = React.memo(({ name }) => <h1>{name}</h1>);

 

Use React.memo when:

  • The component is pure and renders the same output for the same props.
  • The component re-renders frequently without prop changes.

 

2. useMemo – Memoizes Computed Values

  • useMemo caches expensive computations to avoid recalculating them on every render.

Example:

 

const filteredData = useMemo(() => data.filter(item => item.active), [data]);

 

Use useMemo when:

  • A function returns a computed value (e.g., filtering, sorting, heavy calculations).
  • The computation is expensive and should only run when dependencies change.

 

3. useCallback – Memoizes Functions

  • useCallback returns a memoized function, ensuring the function reference doesn’t change unless dependencies update.

Example:

 

const handleClick = useCallback(() => console.log("Clicked!"), []);

 

Use useCallback when:

  • Passing functions as props to child components to prevent unnecessary renders.
  • Preventing function recreation on every render.

Key Differences

Use useCallback when:

  • Passing functions as props to child components to prevent unnecessary renders.
  • Preventing function recreation on every render.

Key Differences

Use useCallback when:

  • Passing functions as props to child components to prevent unnecessary renders.
  • Preventing function recreation on every render.

Key Differences

 

Feature React.memo useMemo useCallback
What it Memoizes Whole component Computed value Function reference
Purpose Prevents re-rendering Avoids recalculating values Prevents function recreation
When to Use Memoize UI components Optimize expensive calculations Optimize function props

 

Conclusion

  • Use React.memo to prevent unnecessary re-renders of components.
  • Use useMemo to cache expensive calculations and avoid re-computing values.
  • Use useCallback to memoize functions, especially when passing them as props.

Using these optimizations correctly can improve React app performance and avoid unnecessary renders.

7.

How do you handle expensive calculations in React components without affecting performance?

Answer

Expensive calculations can slow down React applications if they run on every render. To optimize performance, use memoization, background processing, and efficient state management.

 

1. Use useMemo to Cache Expensive Computations

useMemo caches computed values and only recalculates them when dependencies change.

Example:

 

const filteredData = useMemo(() => {
return data.filter(item => item.active);
}, [data]);

 

Use when:

  • A function performs heavy calculations (e.g., filtering, sorting).
  • The result doesn’t change frequently.

 

2. Use useCallback to Prevent Function Recreation

useCallback memoizes functions so they are not recreated on every render.

Example:

 

const computeValue = useCallback(() => {
return expensiveCalculation(data);
}, [data]);

 

Use when:

  • Passing functions as props to child components.

 

3. Perform Expensive Calculations Outside the UI Thread (Web Workers)

For extremely heavy computations, offload work to Web Workers.

Example:

 

const worker = new Worker("worker.js");
worker.postMessage(data);
worker.onmessage = (e) => console.log(e.data);

 

Use when:

  • The calculation blocks the UI and affects responsiveness.

 

4. Optimize Rendering with React.memo

Use React.memo to prevent unnecessary re-renders of components.

Example:

 

const MemoizedComponent = React.memo(MyComponent);

 

Use when:

  • A component renders frequently with the same props.

 

5. Debounce Expensive Calls (e.g., Search Input)

Use debounce to limit function calls and reduce unnecessary computations.

Example:

 

const debouncedSearch = useCallback(debounce((query) => fetchData(query), 500), []);

 

Use when:

  • Handling real-time user input (e.g., search, autocomplete).

 

Conclusion

To handle expensive calculations efficiently in React:

  • Use useMemo for caching computed values.
  • Use useCallback to optimize function references.
  • Offload heavy tasks to Web Workers if needed.
  • Use React.memo to reduce unnecessary renders.
  • Apply debouncing for frequent event-based computations.

These techniques help keep React apps fast and responsive.

8.

How does lazy loading with React.lazy and Suspense improve performance?

Answer

Lazy loading delays the loading of components until they are needed, reducing the initial JavaScript bundle size and improving performance.

 

How React.lazy Works

React.lazy dynamically imports components only when they are rendered, preventing unnecessary code from loading upfront.

Example:

 

import { lazy, Suspense } from "react";

const LazyComponent = lazy(() => import("./HeavyComponent"));

const App = () => (
<Suspense fallback={<p>Loading...</p>}>
<LazyComponent />
</Suspense>
);

 

How This Improves Performance:

  • Reduces Initial Load Time – Only necessary components are loaded at first.
  • Optimizes Large Applications – Splits code into smaller chunks, improving speed.
  • Enhances User Experience – Displays a fallback UI (Suspense) while loading.

 

How Suspense Works

  • Suspense wraps lazy-loaded components and provides a fallback UI while loading.
  • Prevents UI flickering and keeps users informed during loading.

 

When to Use Lazy Loading?

  • Large components that aren’t needed immediately (e.g., dashboards, charts).
  • Route-based splitting in single-page applications (SPAs).
  • Third-party libraries that don’t need to load upfront.

 

Conclusion

React.lazy and Suspense improve performance by delaying component loading, reducing bundle size, and improving initial load speed, making React apps faster and more efficient.

9.

How does useRef differ from useState, and when should you use it?

Answer

Both useRef and useState store values in React, but they serve different purposes.

 

Key Differences

 

Feature useRef useState
Triggers Re-Renders ❌ No ✅ Yes
Stores Mutable Values ✅ Yes ❌ No (state is immutable)
Accesses DOM Elements ✅ Yes ❌ No
Use Case Persist values without re-renders Store component state that affects rendering

 

When to Use useRef

  • Persist Values Without Causing Re-Renders

 

const countRef = useRef(0);
countRef.current += 1; // Updates without triggering a re-render

 

Best for: Storing previous values, timers, or caching data.

  • Access and Manipulate DOM Elements

 

const inputRef = useRef(null);
<input ref={inputRef} />;

 

Best for: Focusing input fields or interacting with the DOM.

 

When to Use useState

  • Trigger Re-Renders When State Changes

 

const [count, setCount] = useState(0);
setCount(count + 1); // Triggers a re-render

 

Best for: Managing UI-related data that changes over time.

 

Conclusion

  • Use useRef when you need to store values without causing re-renders or access DOM elements.
  • Use useState when the value should trigger a re-render (e.g., UI updates).
10.

What are the use cases for useLayoutEffect over useEffect?

Answer

Both useEffect and useLayoutEffect run after rendering, but useLayoutEffect runs synchronously before the browser paints the screen, while useEffect runs asynchronously after rendering.

 

When to Use useLayoutEffect

1. Measuring DOM Elements Before Paint

Use when you need to calculate layout (e.g., element size, position) before the browser updates the screen.

 

useLayoutEffect(() => {
const height = divRef.current.clientHeight;
console.log("Height before paint:", height);
}, []);

 

Best for: Dynamic layouts, animations, tooltips.

 

2. Preventing UI Flickering

If an effect modifies the DOM before it appears, useLayoutEffect ensures changes happen before the paint.

 

useLayoutEffect(() => {
if (modalOpen) document.body.style.overflow = "hidden";
return () => document.body.style.overflow = "auto";
}, [modalOpen]);

 

Best for: Preventing flashes in modal dialogs or dynamic UI changes.

3. Synchronizing Scroll Position or Focus

 

useLayoutEffect(() => {
window.scrollTo(0, 0);
}, []);

 

Best for: Restoring scroll position when navigating pages.

 

When to Use useEffect Instead

  • For data fetching, event listeners, and updates that don’t block rendering.
  • If DOM measurement or immediate updates aren’t required.

 

Conclusion

  • Use useLayoutEffect when UI updates must happen before paint to prevent flickering.
  • Use useEffect for asynchronous tasks like API calls or event listeners.
11.

How does the dependency array in useEffect work, and what are common mistakes when using it?

Answer

The dependency array in useEffect controls when the effect runs by tracking specific values.

 

How the Dependency Array Works

 

useEffect(() => {
console.log("Effect runs when count changes");
}, [count]); // Runs only when `count` changes

 

Dependency Array Behavior
[] (empty) Runs only once after mount (like componentDidMount).
[var] Runs when var changes (like componentDidUpdate).
No array Runs on every render.

 

Common Mistakes When Using useEffect

 

1. Forgetting Dependencies (Causing Stale State)

 

useEffect(() => {
fetchData(); // Fetches old data if dependencies are missing
}, []); // ❌ Missing dependencies

 

Fix: Always list required dependencies:

 

useEffect(() => {
fetchData(query);
}, [query]); // ✅ Runs when `query` changes

 

2. Using Objects or Arrays as Dependencies

Objects and arrays are reference types, so React sees them as “new” on every render.

 

useEffect(() => {
console.log("Effect runs every time");
}, [{}]); // ❌ Always runs because `{}` is a new object

 

Fix: Use useMemo or primitive values instead.

const memoizedObj = useMemo(() => ({ key: value }), [value]);
useEffect(() => {}, [memoizedObj]); // ✅ Runs only when `value` changes

 

3. Causing Infinite Loops with State Updates

Updating state inside useEffect without proper conditions causes infinite loops.

 

useEffect(() => {
setCount(count + 1); // ❌ Triggers a re-render on every update
}, [count]);

 

Fix: Use functional updates to prevent unnecessary re-renders

 

useEffect(() => {
setCount(prevCount => prevCount + 1);
}, []); // ✅ Runs only once

 

Conclusion

  • Use the dependency array correctly to prevent unnecessary renders.
  • Always list state or props that affect the effect to avoid stale data.
  • Be careful with objects, arrays, and state updates to prevent infinite loops.
12.

How would you create a custom React Hook for fetching data?

Answer

A custom Hook in React allows reusable logic, making code cleaner and more maintainable. A data-fetching Hook simplifies API requests across multiple components.

 

Steps to Create a Custom Hook for Fetching Data

  1. Use useState to manage loading, error, and data states.
  2. Use useEffect to fetch data when the component mounts or dependencies change.

 

Example: useFetch Hook

 

import { useState, useEffect } from "react";

const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch data");
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};

fetchData();
}, [url]); // Runs when `url` changes

return { data, loading, error };
};

export default useFetch;

 

Using the useFetch Hook

 

const Users = () => {
const { data, loading, error } = useFetch("https://api.example.com/users");

if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;

return (
<ul>
{data.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
};

 

Why Use a Custom Hook for Fetching Data?

  • Reusability – Can be used in multiple components.
  • Cleaner Code – Removes redundant fetch logic from components.
  • Better State Management – Handles loading and error states efficiently.

 

Conclusion

Creating a useFetch Hook makes API calls easier to manage, improves code reusability, and keeps components clean and focused on UI logic.

13.

How do you manage complex state updates using useReducer?

Answer

useReducer is a React Hook used for managing complex state logic in a structured way. It is an alternative to useState, especially when multiple related state updates need to be handled.

 

How useReducer Works

  • Takes a reducer function that determines how state changes based on actions.
  • Uses a dispatch function to trigger state updates.
  • Returns the updated state.

 

Example: Managing a Counter with useReducer

 

import { useReducer } from "react";

// Reducer function
const 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:
return state;
}
};

// Component using useReducer
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });

return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
);
};

 

When to Use useReducer Instead of useState?

 

Feature useState useReducer
Best for Simple state updates Complex state logic
State structure Single values Objects with multiple fields
State updates Directly with setState Uses an action-based approach
Performance Can cause unnecessary re-renders Optimized for structured state updates

 

Use useReducer when:

  • The state has multiple related values (e.g., form handling).
  • The state transitions are complex (e.g., authentication flow).
  • The next state depends on the previous state.

 

Conclusion

useReducer simplifies managing complex state logic by using a structured action-based approach, improving code organization and maintainability.

14.

How does React Router handle dynamic routes, and how do you extract URL parameters?

Answer

React Router allows dynamic routing by defining route parameters (:id) in the URL. These parameters can be accessed in components using the useParams Hook.

 

1. Defining a Dynamic Route

Dynamic segments (:id) allow URLs to change based on parameters.

 

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import UserProfile from "./UserProfile";

const App = () => (
<Router>
<Routes>
<Route path="/user/:id" element={<UserProfile />} />
</Routes>
</Router>
);

 

Example URLs: /user/1, /user/42

 

2. Extracting URL Parameters Using useParams

The useParams Hook retrieves parameters from the URL.

 

import { useParams } from "react-router-dom";

const UserProfile = () => {
const { id } = useParams(); // Extracts "id" from URL

return <h1>User ID: {id}</h1>;
};

 

Use when: You need to display or fetch data based on dynamic routes.

 

3. Navigating to a Dynamic Route

Use the useNavigate Hook to programmatically navigate with dynamic parameters.

 

import { useNavigate } from "react-router-dom";

const Home = () => {
const navigate = useNavigate();

return <button onClick={() => navigate("/user/10")}>Go to User 10</button>;
};

 

Conclusion

React Router supports dynamic routes using :id, and you can extract URL parameters using useParams(). This is useful for user profiles, product pages, and other dynamic content-driven applications.

15.

What is the difference between useNavigate and useHistory in React Router?

Answer

Difference Between useNavigate and useHistory in React Router

In React Router v6, useHistory was replaced by useNavigate for navigation within an app.

 

1. useHistory (React Router v5 – Deprecated)

  • Used to navigate programmatically.
  • Provided a history object with push, replace, and goBack methods.

Example (React Router v5)

 

import { useHistory } from "react-router-dom";

const Component = () => {
const history = useHistory();

return <button onClick={() => history.push("/dashboard")}>Go to Dashboard</button>;
};

 

Deprecated in React Router v6

 

2. useNavigate (React Router v6 – Current)

  • Replaces useHistory with a simpler navigate function.
  • Supports relative and absolute paths.

Example (React Router v6)

 

import { useNavigate } from "react-router-dom";

const Component = () => {
const navigate = useNavigate();

return <button onClick={() => navigate("/dashboard")}>Go to Dashboard</button>;
};

 

Advantages of useNavigate:

  • Simpler syntax (navigate(path)) compared to history.push(path).
  • Supports replace and state (like history.replace).

Example: Replacing History Entry

 

navigate("/login", { replace: true }); // Prevents going back to the previous page

 

Key Differences

 

Feature useHistory (v5) useNavigate (v6)
Status Deprecated Current
Navigation history.push("/path") navigate("/path")
Replace Current Page history.replace("/path") navigate("/path", { replace: true })
Go Back history.goBack() navigate(-1)

 

Conclusion

  • Use useNavigate in React Router v6, as useHistory is deprecated.
  • It provides a simpler, more flexible way to navigate between pages.
16.

How does server-side rendering (SSR) work with React Router?

Answer

Server-Side Rendering (SSR) in React generates the HTML on the server before sending it to the client. This improves performance, SEO, and initial page load speed. React Router supports SSR by handling routing on the server side.

How SSR Works with React Router

  1. Client Requests a Page – The browser requests a URL (e.g., /dashboard).
  2. Server Renders the React Component – The server pre-renders the matching route into HTML.
  3. HTML Sent to Client – The user sees the initial content immediately.
  4. Hydration – React attaches event listeners and makes the page interactive.

Example: SSR with React Router in Next.js

Next.js is a popular React framework with built-in SSR support.

1. Creating an SSR Page in Next.js

 

export async function getServerSideProps() {
const res = await fetch("https://api.example.com/data");
const data = await res.json();

return { props: { data } };
}

const Page = ({ data }) => <h1>{data.title}</h1>;

export default Page;

 

getServerSideProps fetches data on the server before rendering.

 

Advantages of SSR with React Router

  • Better SEO – Search engines can index pre-rendered content.
  • Faster Initial Load – The user sees content immediately, even before React hydrates.
  • Improved Performance – Reduces JavaScript execution time on the client side.

 

When to Use SSR?

  • SEO-heavy pages (blogs, e-commerce).
  • Dynamic content that requires fresh data on each request.
  • Performance-sensitive applications.

 

Conclusion

React Router supports SSR by pre-rendering pages on the server, improving SEO and performance. Next.js is the preferred way to implement SSR in React applications.

17.

What are the key differences between HashRouter, BrowserRouter, and MemoryRouter?

Answer

Key Differences Between HashRouter, BrowserRouter, and MemoryRouter

React Router provides different routing strategies based on the application’s environment and requirements.

 

1. BrowserRouter (Default for Web Apps)

  • Uses the HTML5 History API (pushState and replaceState).
  • Clean, user-friendly URLs (/about instead of #/about).
  • Requires server-side configuration to handle direct page reloads.

Example:

 

import { BrowserRouter, Route, Routes } from "react-router-dom";

<BrowserRouter>
<Routes>
<Route path="/home" element={<Home />} />
</Routes>
</BrowserRouter>;

 

Best for: Standard web apps with a backend that supports route handling.

 

2. HashRouter (Uses URL Hash for Routing)

  • Uses the URL hash (#) to store routes (/#/about instead of /about).
  • No server configuration required (works in static file hosting).
  • Less SEO-friendly than BrowserRouter.

Example:

 

import { HashRouter } from "react-router-dom";

<HashRouter>
<Routes>
<Route path="/home" element={<Home />} />
</Routes>
</HashRouter>;

 

Best for: Static websites (e.g., GitHub Pages) where the server doesn’t support route handling.

 

3. MemoryRouter (Stores Routes in Memory)

  • Keeps navigation history in memory, not in the browser’s URL.
  • Doesn’t update the browser URL.
  • Useful for testing and non-browser environments (e.g., React Native).

Example:

 

import { MemoryRouter } from "react-router-dom";

<MemoryRouter>
<Routes>
<Route path="/home" element={<Home />} />
</Routes>
</MemoryRouter>;

 

Best for: Unit tests and React Native apps.

Comparison Table

 

Feature BrowserRouter HashRouter MemoryRouter
URL Format /about /#/about No URL change
Requires Server Setup? ✅ Yes ❌ No ❌ No
SEO-Friendly? ✅ Yes ❌ No ❌ No
Use Case Web apps Static sites Testing, React Native

 

Conclusion

  • Use BrowserRouter for most web apps with a server.
  • Use HashRouter for static sites where server configuration isn’t possible.
  • Use MemoryRouter for testing and environments where no browser history is needed.
18.

How do you test React components that rely on async data fetching?

Answer

Testing components that fetch data asynchronously requires handling delays, promises, and UI state updates. React Testing Library (RTL) and Jest provide tools for simulating async behavior.

 

1. Example Component with Async Data Fetching

 

import { useState, useEffect } from "react";

const UserList = () => {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
});
}, []);

if (loading) return <p>Loading...</p>;

return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};

export default UserList;

 

2. Writing an Async Test with Jest & React Testing Library

 

import { render, screen, waitFor } from "@testing-library/react";
import UserList from "./UserList";

// Mock fetch API
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve([{ id: 1, name: "John Doe" }]),
})
);

test("renders user list after fetching data", async () => {
render(<UserList />);

// Check for loading state
expect(screen.getByText(/loading/i)).toBeInTheDocument();

// Wait for users to appear in the DOM
await waitFor(() => expect(screen.getByText("John Doe")).toBeInTheDocument());
});

 

What This Test Does:

  • Mocks the fetch API to return fake data.
  • Asserts that the loading state appears first.
  • Waits for the data to be rendered before testing.

3. Best Practices for Testing Async Components

  • Use waitFor() – Waits for the component to update after async calls.
  • Mock API Requests – Prevents real network calls, making tests fast and reliable.
  • Check Loading and Error States – Ensure the component handles different states correctly.

 

Conclusion

To test async components:

  • Mock API calls using Jest.
  • Use waitFor to wait for UI updates.
  • Test loading and final state to ensure correct behavior.

This ensures reliable and efficient testing for data-fetching React components.

 

19.

What are the best practices for testing React components using Jest and React Testing Library?

Answer

React Testing Library (RTL) and Jest provide a robust way to test React components by focusing on user interactions and UI behavior rather than implementation details.

 

1. Test Components Like a User Would

Use screen.getByText and screen.getByRole instead of querying by class names or IDs.

 

render(<button>Click Me</button>);
expect(screen.getByText("Click Me")).toBeInTheDocument();

 

Why? Ensures tests are resilient to implementation changes.

 

2. Use waitFor for Asynchronous Tests

Handle async data fetching with waitFor.

 

await waitFor(() => expect(screen.getByText("User Loaded")).toBeInTheDocument());

 

Why? Prevents tests from failing due to delayed state updates.

 

3. Avoid Testing Implementation Details

Bad (Testing Internal State)

 

expect(component.state.count).toBe(1); // ❌ Avoid this

 

Good (Testing User Behavior)

 

fireEvent.click(screen.getByText("Increment"));
expect(screen.getByText("Count: 1")).toBeInTheDocument();

 

Why? Tests should focus on how the UI behaves, not how it’s implemented.

 

4. Mock API Calls Instead of Making Real Requests

Use jest.fn() or msw to mock API responses.

 

global.fetch = jest.fn(() =>
Promise.resolve({ json: () => Promise.resolve({ name: "John Doe" }) })
);

 

Why? Avoids real API calls, making tests fast and reliable.

 

5. Use act() for State Updates in Tests using React Testing Library

Ensure updates happen synchronously using act().

 

import { act } from "@testing-library/react";

act(() => {
setState(newState);
});

 

Why? Helps prevent warnings when React state updates asynchronously.

 

6. Clean Up After Tests using React Testing Library

Use afterEach(cleanup) to reset the DOM between tests.

 

import { cleanup } from "@testing-library/react";

afterEach(() => {
cleanup();
});

Why? Prevents test pollution and inconsistent results.

 

Conclusion

To write reliable and maintainable tests:

  • Test UI behavior, not implementation details.
  • Mock APIs for predictable results.
  • Use waitFor for async updates and act for state changes.
  • Ensure accessibility by using getByRole and getByText.

Following these best practices improves test reliability, maintainability, and performance in React applications.

20.

How do you handle testing for React components that use the Context API?

Answer

When testing React components that use the Context API, you need to wrap the component inside the provider and ensure the correct context values are passed.

 

1. Example: Context API Component

Let’s assume we have an AuthContext that provides user authentication data.

 

import { createContext, useContext } from "react";

const AuthContext = createContext();

export const AuthProvider = ({ children }) => (
<AuthContext.Provider value={{ user: { name: "John Doe" } }}>
{children}
</AuthContext.Provider>
);

export const useAuth = () => useContext(AuthContext);

 

A component consuming this context:

 

const UserProfile = () => {
const { user } = useAuth();
return <h1>{user ? `Welcome, ${user.name}` : "Guest"}</h1>;
};

export default UserProfile;

 

2. Testing a Context Consumer

To test UserProfile, we must wrap it with AuthProvider in the test.

 

import { render, screen } from "@testing-library/react";
import { AuthProvider } from "./AuthContext";
import UserProfile from "./UserProfile";

test("displays the logged-in user", () => {
render(
<AuthProvider>
<UserProfile />
</AuthProvider>
);

expect(screen.getByText("Welcome, John Doe")).toBeInTheDocument();
});

 

Why? This ensures the component correctly receives context values.

 

3. Mocking Context for Different Scenarios

Instead of using AuthProvider, we can manually mock the context to test different cases.

 

import { render, screen } from "@testing-library/react";
import { AuthContext } from "./AuthContext";
import UserProfile from "./UserProfile";

test("displays guest message when user is not logged in", () => {
render(
<AuthContext.Provider value={{ user: null }}>
<UserProfile />
</AuthContext.Provider>
);

expect(screen.getByText("Guest")).toBeInTheDocument();
});

 

Why? This allows us to test multiple context states (e.g., logged-in vs. guest).

 

Conclusion

  • Wrap components with the Context Provider when testing context-dependent components.
  • Mock the Context Provider to test different scenarios.
  • Use screen.getByText to verify UI behavior.

Following these steps ensures reliable and isolated tests for Context API components.

21.

What is the difference between unit testing and integration testing in React applications?

Answer

Unit and integration testing ensure React applications work correctly, but they focus on different aspects of the application.

 

1. Unit Testing (Isolated Component Testing)

  • Tests individual components or functions in isolation.
  • Ensures that a specific function or UI element behaves as expected.
  • Uses Jest and React Testing Library to verify component rendering and logic.

Example: Unit Testing a Button Component

 

import { render, screen } from "@testing-library/react";
import Button from "./Button";

test("renders button with correct label", () => {
render(<Button label="Click Me" />);
expect(screen.getByText("Click Me")).toBeInTheDocument();
});

 

Best for: Testing pure functions, UI elements, and hooks.

 

2. Integration Testing (Multiple Components Working Together)

  • Tests how multiple components interact with each other.
  • Ensures that a feature works as a whole (e.g., form submission, API requests).
  • Often includes mocking API calls and state management tests.

Example: Integration Testing a Form Submission

 

import { render, screen, fireEvent } from "@testing-library/react";
import LoginForm from "./LoginForm";

test("submits form with correct input", () => {
render(<LoginForm />);

fireEvent.change(screen.getByLabelText("Username"), { target: { value: "user1" } });
fireEvent.click(screen.getByText("Submit"));

expect(screen.getByText("Login successful")).toBeInTheDocument();
});

 

Best for: Forms, API calls, and state management testing.

Key Differences

 

Feature Unit Testing Integration Testing
Scope Individual component/function Multiple components working together
Focus Internal logic, rendering Data flow, state updates, API calls
Tools Jest, React Testing Library Jest, React Testing Library, MSW (Mock API)
Use Case UI elements, functions, hooks Forms, API calls, authentication

 

Conclusion

  • Use Unit Testing for isolated component behavior.
  • Use Integration Testing to verify how components work together.
  • Both are essential for building a reliable React application.

 

22.

How does React integrate with GraphQL for data fetching?

Answer

React integrates with GraphQL using libraries like Apollo Client and Relay, allowing efficient data fetching, caching, and state management.

 

1. Setting Up Apollo Client in React

Apollo Client is the most popular GraphQL client for React applications.

Install Apollo Client

npm install @apollo/client graphql

 

Configure Apollo Provider

 

import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";

const client = new ApolloClient({
uri: "https://api.example.com/graphql",
cache: new InMemoryCache(),
});

const App = () => (
<ApolloProvider client={client}>
<MyComponent />
</ApolloProvider>
);

 

2. Fetching Data with useQuery Hook

GraphQL queries are used to fetch only the required data.

Example: Fetching Users




import { gql, useQuery } from "@apollo/client";

const GET_USERS = gql`
query {
users {
id
name
email
}
}
`;

const UserList = () => {
const { loading, error, data } = useQuery(GET_USERS);

if (loading) return <p>Loading...</p>;
if (error) return <p>Error fetching data</p>;

return (
<ul>
{data.users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};

 

Advantages:

  • Fetches only required fields (reduces over-fetching).
  • Handles loading and error states automatically.

3. Mutations: Sending Data to GraphQL

Mutations allow modifying data on the server.

Example: Adding a New User

 

import { gql, useMutation } from "@apollo/client";

const ADD_USER = gql`
mutation AddUser($name: String!, $email: String!) {
addUser(name: $name, email: $email) {
id
name
}
}
`;

const AddUserForm = () => {
const [addUser] = useMutation(ADD_USER);

const handleSubmit = () => {
addUser({ variables: { name: "John Doe", email: "john@example.com" } });
};

return <button onClick={handleSubmit}>Add User</button>;
};

 

Conclusion

  • Use Apollo Client for easy GraphQL integration in React.
  • Use useQuery for fetching data and useMutation for modifying data.
  • GraphQL helps reduce over-fetching and provides a more efficient data structure.

This approach makes React applications faster, scalable, and more efficient for data-driven apps.

23.

What are the benefits of using TypeScript with React, and how does it improve component development?

Answer

TypeScript enhances React development by adding static typing, improving code quality, maintainability, and developer experience.

1. Type Safety and Early Error Detection

  • Prevents runtime errors by catching issues at compile time.
  • Helps developers avoid common mistakes, such as passing incorrect props.

 

type ButtonProps = { label: string };
const Button: React.FC<ButtonProps> = ({ label }) => <button>{label}</button>;

 

Ensures label is always a string, reducing bugs.

2. Better Developer Experience (Intellisense & Autocomplete)

  • Provides autocompletion, type hints, and documentation in IDEs.
  • Makes it easier to understand function signatures and props.

 

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {};

 

Prevents incorrect event handling by enforcing proper event types.

3. Strongly Typed Props and State

  • Helps document component props and state structure explicitly.

 

type User = { id: number; name: string };
const [user, setUser] = useState<User | null>(null);

 

Prevents incorrect state assignments and improves maintainability.

4. Improved Code Maintainability & Scalability

  • Enforces consistent types across a project.
  • Makes refactoring safer and easier to manage in large applications.

 

interface User {
id: number;
name: string;
}

const fetchUser = async (): Promise<User> => {
return { id: 1, name: "John Doe" };
};

 

Ensures API responses match expected types.

5. Better Control with Redux and Context API

  • Defines action types, reducers, and store structure explicitly.
  • Prevents type mismatches in state management.

 

interface AuthState {
user: User | null;
}

const authReducer = (state: AuthState, action: { type: string; payload?: User }) => {
switch (action.type) {
case "LOGIN":
return { ...state, user: action.payload };
default:
return state;
}
};

 

Conclusion

Using TypeScript with React provides:

  • Type safety, reducing runtime errors.
  • Better developer experience with auto-completion and hints.
  • Stronger code maintainability for large applications.

It makes React apps more reliable, scalable, and easier to debug.

24.

How would you handle WebSockets or real-time updates in a React application?

Answer

WebSockets enable real-time communication between the client and server, making them ideal for live chat, notifications, and real-time dashboards in React applications.

1. Using Native WebSockets in React

React’s useEffect and useState can manage WebSocket connections efficiently.

Example: WebSocket Connection in a Component

 

import { useEffect, useState } from "react";

const WebSocketComponent = () => {
const [messages, setMessages] = useState([]);

useEffect(() => {
const socket = new WebSocket("wss://example.com/socket");

socket.onmessage = (event) => {
setMessages(prev => [...prev, event.data]);
};

return () => socket.close(); // Cleanup on unmount
}, []);

return (
<ul>
{messages.map((msg, index) => (
<li key={index}>{msg}</li>
))}
</ul>
);
};

export default WebSocketComponent;

 

Why?

  • Opens a persistent connection with the server.
  • Listens for incoming messages and updates state.
  • Closes connection when the component unmounts.

2. Using socket.io for WebSockets

For better event handling and reconnection, use Socket.IO.

Installation

 

npm install socket.io-client

 

Example: Using Socket.IO in React

import { useEffect, useState } from "react";
import { io } from "socket.io-client";

const socket = io("https://example.com");

const ChatComponent = () => {
const [messages, setMessages] = useState([]);

useEffect(() => {
socket.on("message", (msg) => {
setMessages(prev => [...prev, msg]);
});

return () => socket.off("message");
}, []);

return (
<ul>
{messages.map((msg, index) => (
<li key={index}>{msg}</li>
))}
</ul>
);
};

 

Why?

  • Automatic reconnection if connection drops.
  • Supports rooms, namespaces, and event-based messaging.

3. Optimizing Performance for Real-Time Data

  • Debounce state updates to prevent excessive re-renders.
  • Use Web Workers for processing real-time data.
  • Use Redux or Zustand for managing live updates efficiently.

Conclusion

To handle WebSockets in React:

  • Use native WebSockets for basic real-time communication.
  • Use Socket.IO for advanced event handling and reconnection.
  • Optimize state updates to improve performance.

This approach ensures fast and reliable real-time updates in React applications.

25.

How does React interact with Web Workers for performance-heavy tasks?

Answer

Web Workers allow React applications to run CPU-intensive tasks (e.g., data processing, image manipulation) in a separate thread, preventing UI freezes and improving performance.

1. Creating a Web Worker in React

A Web Worker runs JavaScript code in the background, separate from the main thread.

Example: Web Worker File (worker.js)

 

self.onmessage = (event) => {
const result = event.data * 2; // Simulating heavy computation
postMessage(result);
};

 

2. Using Web Workers in a React Component

React communicates with the Web Worker via postMessage and onmessage.

 

import { useState, useEffect } from "react";

const HeavyComputation = () => {
const [result, setResult] = useState(null);

useEffect(() => {
const worker = new Worker(new URL("./worker.js", import.meta.url));

worker.postMessage(10); // Send data to worker
worker.onmessage = (event) => setResult(event.data); // Receive processed data

return () => worker.terminate(); // Cleanup on unmount
}, []);

return <p>Computed Result: {result}</p>;
};

export default HeavyComputation;

 

Why Use Web Workers?

  • Offloads heavy computations from the main thread.
  • Prevents UI blocking and lag in React apps.
  • Ideal for large data processing, video encoding, and AI computations.

3. Using Web Workers with comlink for Simpler Integration

comlink abstracts Web Worker communication, making it easier to use.

 

npm install comlink

 

Example Using comlink

 

import { wrap } from "comlink";

const worker = new Worker(new URL("./worker.js", import.meta.url));
const workerAPI = wrap(worker);

const result = await workerAPI.heavyTask(10);

 

Why? Reduces complexity by eliminating postMessage boilerplate.

Conclusion

  • Use Web Workers to run heavy computations without blocking the UI.
  • Communicate using postMessage or libraries like comlink.
  • Always terminate workers when not needed to free resources.

This ensures smooth and responsive React applications for performance-intensive tasks.

26.

What are the key considerations when migrating a legacy React codebase to Next.js?

Answer

Migrating a React app to Next.js improves performance, SEO, and scalability, but requires careful planning.

1. Understanding Next.js Features

  • Server-Side Rendering (SSR) – Pre-renders pages for better performance & SEO.
  • Static Site Generation (SSG) – Builds static pages at compile time.
  • API Routes – Backend functionality inside Next.js (/pages/api).

2. Restructuring the Project

Next.js follows a file-based routing system, so you’ll need to move components accordingly:

Convert src/components to components/ (No major changes needed).

Move src/pages to /app (Next.js app router handles routing automatically).

Replace react-router-dom with Next.js routing (next/link, next/router).

3. Updating Routing Logic

React Router (Legacy Code)

 

import { BrowserRouter, Route } from "react-router-dom";
<BrowserRouter>
<Route path="/about" component={About} />
</BrowserRouter>;

 

Next.js Routing (New Code)

 

import Link from "next/link";

<Link href="/about">Go to About</Link>;

 

Next.js Dynamic Routing

 

// pages/user/[id].js
import { useRouter } from "next/router";

const User = () => {
const { query } = useRouter();
return <h1>User ID: {query.id}</h1>;
};

export default User;

 

4. Handling Data Fetching

  • Replace useEffect API calls with Next.js data-fetching methods if you don’t need client-side rendering:
    • getServerSideProps() (SSR)
    • getStaticProps() (SSG)

 

export async function getServerSideProps() {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
return { props: { data } };
}

 

5. Managing Static Assets and Styles

Move /public assets (images, icons) to Next.js /public/ folder.

Use CSS modules (.module.css) or styled-components instead of global CSS.

6. Updating API Calls (Optional)

Next.js has built-in API routes, so you can move API logic from the frontend to /app/api.

 

// app/api/{somepath}/router.js
export async function GET(request) {
return new Response(JSON.stringify({ message: "Hello from Next.js API!" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}

 

7. SEO and Performance Optimization

Use next/head for SEO metadata.

Use next/image for optimized image loading.

Enable Automatic Static Optimization for faster page loads.

Conclusion

Migrating to Next.js involves:

  • Restructuring the project for file-based routing.
  • Updating navigation & API calls for SSR/SSG.
  • Optimizing SEO & performance using Next.js features.

This transition enhances performance, scalability, and developer experience while keeping the core React logic intact.

React.js Coding tasks

1.

Counter with Undo/Redo (useState)

Answer
  • Implement a counter with increment, decrement, reset, and undo/redo functionality.

Here is the implementation of a Counter Component with Undo/Redo functionality using useState:

Features:

  • Increment, Decrement, and Reset counter.
  • Undo/Redo functionality to revert or restore previous values.
  • Prevents unnecessary state updates when there’s nothing to undo or redo.

Counter with Undo/Redo (useState)

 

import { useState } from "react";

const CounterWithUndoRedo = () => {
const [count, setCount] = useState(0);
const [history, setHistory] = useState([0]); // Stores past counter values
const [index, setIndex] = useState(0); // Tracks current position in history

const updateCount = (newCount) => {
const updatedHistory = history.slice(0, index + 1); // Trim future states
setHistory([...updatedHistory, newCount]);
setIndex(updatedHistory.length);
setCount(newCount);
};

const increment = () => updateCount(count + 1);
const decrement = () => updateCount(count > 0 ? count - 1 : 0);
const reset = () => updateCount(0);

const undo = () => {
if (index > 0) {
setIndex(index - 1);
setCount(history[index - 1]);
}
};

const redo = () => {
if (index < history.length - 1) {
setIndex(index + 1);
setCount(history[index + 1]);
}
};

return (
<div style={{ textAlign: "center", marginTop: "20px" }}>
<h2>Counter: {count}</h2>
<button onClick={increment}>Increment</button>
<button onClick={decrement} disabled={count === 0}>Decrement</button>
<button onClick={reset}>Reset</button>
<button onClick={undo} disabled={index === 0}>Undo</button>
<button onClick={redo} disabled={index === history.length - 1}>Redo</button>
</div>
);
};

export default CounterWithUndoRedo;

 

How It Works:

  1. State Tracking:
    • count: Stores the current counter value.
    • history: Maintains a list of past counter values.
    • index: Tracks the current position in history.
  2. Updating State:
    • When increment, decrement, or reset is triggered, it updates the counter and adds the new value to the history.
    • undo moves back in the history array without modifying it.
    • redo moves forward in the history if there are available future values.
  3. Button Disabling:
    • Decrement is disabled if count is 0.
    • Undo is disabled if there’s no previous state.
    • Redo is disabled if there’s no future state.

Why This Approach?

  • Ensures a clear undo/redo history with efficient state updates.
  • Prevents unnecessary updates by trimming future states when new actions are taken.
  • Maintains a simple and readable structure while implementing full undo/redo functionality.
  • 2. Toggle Theme (useState & CSS Variables)2. Implement the Theme Toggle Component
2.

Debounced Search (useState & useEffect)

Answer
  • Implement a search input that updates results only after the user stops typing (debouncing).

This component implements a search input with debounce functionality, ensuring that API calls or expensive computations only trigger after the user stops typing.

  • Uses useState for managing search input.
  • Uses useEffect with setTimeout for debouncing input changes.
  • Prevents unnecessary re-renders and API calls on every keystroke.

1. Implement the Debounced Search Component

 

import { useState, useEffect } from "react";

const DebouncedSearch = () => {
const [searchTerm, setSearchTerm] = useState(""); // Stores user input
const [debouncedTerm, setDebouncedTerm] = useState(""); // Stores debounced value

useEffect(() => {
// Set a delay before updating the debounced value
const timer = setTimeout(() => {
setDebouncedTerm(searchTerm);
}, 500); // Adjust debounce delay (500ms)

return () => clearTimeout(timer); // Cleanup timeout on each input change
}, [searchTerm]);

return (
<div style={{ textAlign: "center", marginTop: "20px" }}>
<h2>Debounced Search</h2>
<input
type="text"
placeholder="Search..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<p>Debounced Value: {debouncedTerm}</p>
</div>
);
};

export default DebouncedSearch;

 

How It Works:

  1. User types in the input fieldsearchTerm updates immediately.
  2. Debounced effect triggers after 500ms of inactivitydebouncedTerm updates.
  3. If the user types before 500ms, the timeout resets, preventing unnecessary updates.
  4. Debounced value is used for API calls or expensive computations instead of updating on every keystroke.

Why This Approach?

  • Reduces API calls by ensuring requests only happen after a delay.
  • Improves performance by preventing unnecessary re-renders.
  • Easily configurable → Adjust setTimeout delay as needed.
3.

Multi-Step Form (useState)

Answer
  • Create a 3-step form (e.g., personal info, address, confirmation).
  • Allow navigation between steps.

Multi-Step Form (useState)

This component implements a 3-step form that allows users to:

  • Enter personal information (Step 1).
  • Enter address details (Step 2).
  • Review and submit the form (Step 3).
  • Navigate between steps using Next & Back buttons.

1. Implement the Multi-Step Form Component

 

import { useState } from "react";

const MultiStepForm = () => {
const [step, setStep] = useState(1); // Track current step
const [formData, setFormData] = useState({
name: "",
email: "",
address: "",
city: "",
});

// Handle input changes
const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};

// Move to the next step
const nextStep = () => setStep((prev) => prev + 1);

// Move to the previous step
const prevStep = () => setStep((prev) => prev - 1);

// Handle form submission
const handleSubmit = (e) => {
e.preventDefault();
alert("Form submitted successfully!");
console.log("Submitted Data:", formData);
};

return (
<div style={{ textAlign: "center", maxWidth: "400px", margin: "auto" }}>
<h2>Multi-Step Form</h2>

{/* Step 1: Personal Info */}
{step === 1 && (
<div>
<h3>Step 1: Personal Info</h3>
<input
type="text"
name="name"
placeholder="Full Name"
value={formData.name}
onChange={handleChange}
/>
<br />
<input
type="email"
name="email"
placeholder="Email"
value={formData.email}
onChange={handleChange}
/>
<br />
<button onClick={nextStep} disabled={!formData.name || !formData.email}>
Next
</button>
</div>
)}

{/* Step 2: Address */}
{step === 2 && (
<div>
<h3>Step 2: Address</h3>
<input
type="text"
name="address"
placeholder="Street Address"
value={formData.address}
onChange={handleChange}
/>
<br />
<input
type="text"
name="city"
placeholder="City"
value={formData.city}
onChange={handleChange}
/>
<br />
<button onClick={prevStep}>Back</button>
<button onClick={nextStep} disabled={!formData.address || !formData.city}>
Next
</button>
</div>
)}

{/* Step 3: Confirmation */}
{step === 3 && (
<div>
<h3>Step 3: Confirmation</h3>
<p><strong>Name:</strong> {formData.name}</p>
<p><strong>Email:</strong> {formData.email}</p>
<p><strong>Address:</strong> {formData.address}, {formData.city}</p>
<button onClick={prevStep}>Back</button>
<button onClick={handleSubmit}>Submit</button>
</div>
)}
</div>
);
};

export default MultiStepForm;

 

How It Works:

  1. step state controls navigation between Step 1, Step 2, and Step 3.
  2. formData stores user input, updating as users type.
  3. Next button is disabled if required fields are empty.
  4. Submit button displays form data and logs it to the console.

Why This Approach?

  • Simple state-based navigation using useState.
  • Prevents incomplete submissions with validation.
  • Encapsulated steps for easy expansion (e.g., adding more fields).
4.

Countdown Timer (useEffect & useRef)

Answer
  • Build a timer that counts down from a given time and stops at zero.
  • Provide start, pause, and reset buttons.

Countdown Timer (useEffect & useRef)

This component implements a countdown timer that:

  • Starts counting down from a given time.
  • Stops at zero when the countdown completes.
  • Provides Start, Pause, and Reset buttons.
  • Uses useEffect for handling side effects.
  • Uses useRef to store the timer reference and prevent unnecessary re-renders.

1. Implement the Countdown Timer Component

 

import { useState, useEffect, useRef } from "react";

const CountdownTimer = () => {
const [time, setTime] = useState(60); // Initial countdown time (in seconds)
const [isRunning, setIsRunning] = useState(false);
const timerRef = useRef(null); // Store interval reference

// Effect to handle countdown
useEffect(() => {
if (isRunning && time > 0) {
timerRef.current = setInterval(() => {
setTime((prevTime) => prevTime - 1);
}, 1000);
} else {
clearInterval(timerRef.current);
}
return () => clearInterval(timerRef.current); // Cleanup on unmount
}, [isRunning, time]);

// Start countdown
const startTimer = () => {
if (time > 0) setIsRunning(true);
};

// Pause countdown
const pauseTimer = () => {
setIsRunning(false);
};

// Reset countdown
const resetTimer = () => {
setIsRunning(false);
setTime(60); // Reset time to initial value
};

return (
<div style={{ textAlign: "center", marginTop: "20px" }}>
<h2>Countdown Timer</h2>
<h1>{time}s</h1>
<button onClick={startTimer} disabled={isRunning || time === 0}>Start</button>
<button onClick={pauseTimer} disabled={!isRunning}>Pause</button>
<button onClick={resetTimer}>Reset</button>
</div>
);
};

export default CountdownTimer;

 

How It Works:

  1. useState for time tracking → Stores the countdown value and running state.
  2. useEffect for timer control → Runs when isRunning changes.
  3. useRef for interval management → Ensures the interval clears when needed.
  4. Prevents negative countdown → Stops at zero automatically.
  5. Disables inappropriate buttons → Prevents invalid actions (e.g., “Start” when running).

Why This Approach?

  • Prevents memory leaks using useRef to manage intervals.
  • Prevents re-renders when toggling start/stop.
  • User-friendly UI with disabled button states.
5.

Fetch & Display Data (useEffect)

Answer
  • Fetch data from an API (https://jsonplaceholder.typicode.com/users).
  • Display a loading indicator and handle errors.

Fetch & Display Data (useEffect)

This component fetches user data from an API, handles loading and errors, and displays the results in a list.

  • Uses useEffect to fetch data when the component mounts.
  • Handles loading and errors gracefully.
  • Displays fetched data dynamically.

1. Implement the Fetch & Display Component

import { useState, useEffect } from "react";

const UserList = () => {
const [users, setUsers] = useState([]); // Store user data
const [loading, setLoading] = useState(true); // Track loading state
const [error, setError] = useState(null); // Store error messages

useEffect(() => {
const fetchUsers = async () => {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
if (!response.ok) {
throw new Error("Failed to fetch users");
}
const data = await response.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false); // Ensure loading stops after fetch completes
}
};

fetchUsers();
}, []); // Empty dependency array ensures it runs once on mount

if (loading) return <p>Loading users...</p>;
if (error) return <p>Error: {error}</p>;

return (
<div style={{ textAlign: "center", marginTop: "20px" }}>
<h2>User List</h2>
<ul>
{users.map(user => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
</div>
);
};

export default UserList;

 

How It Works:

  1. useState manages data, loading, and error states.
  2. useEffect triggers the API call on mount.
  3. Handles API errors with a try-catch block.
  4. Prevents re-fetching by using an empty dependency array ([]).
  5. Displays different UI states:
    • "Loading users..." while fetching.
    • "Error: message" if the fetch fails.
    • List of users once data is loaded.

Why This Approach?

  • Prevents multiple API calls (fetches once on mount).
  • Handles network errors properly.
  • User-friendly experience with a loading indicator.
6.

Infinite Scroll (useEffect & Intersection Observer)

Answer

Infinite Scroll (useEffect & Intersection Observer)

This component implements infinite scrolling, where more items load dynamically as the user scrolls down.

  • Uses useEffect to fetch paginated data.
  • Uses Intersection Observer API to detect when the user reaches the bottom.
  • Prevents unnecessary API calls by tracking loading state.

 

Implement the Infinite Scroll Component

  • Fetch paginated data and load more items when the user scrolls down.

 

import {useState, useEffect, useRef} from "react";

const InfiniteScroll = () => {
	const [items, setItems] = useState([]); // Stores fetched items
	const [page, setPage] = useState(1); // Current page
	const [loading, setLoading] = useState(false); // Loading state
	const [hasMore, setHasMore] = useState(true); // Whether more data is available
	const observerRef = useRef(null); // Reference for the last element
	const loadingRef = useRef(loading);

	// Keep the loadingRef updated
	useEffect(() => {
		loadingRef.current = loading;
	}, [loading]);

	// Fetch data whenever the page changes
	useEffect(() => {
		const fetchItems = async () => {
			setLoading(true);
			try {
				const response = await fetch(
					`https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${page}`
				);
				const data = await response.json();
				if (data.length === 0) {
					// No more data available, stop further fetches
					setHasMore(false);
				} else {
					setItems((prevItems) => [...prevItems, ...data]);
				}
			} catch (error) {
				console.error("Error fetching data:", error);
			} finally {
				setLoading(false);
			}
		};

		if (hasMore) {
			fetchItems();
		}
	}, [page, hasMore]);

	// Set up the observer only once
	useEffect(() => {
		const observer = new IntersectionObserver(
			(entries) => {
				const entry = entries[0];
				// If the observed element is in view, not loading, and more data is available, load next page
				if (entry.isIntersecting && !loadingRef.current && hasMore) {
					setPage((prevPage) => prevPage + 1);
				}
			},
			{
				// Adjust threshold as needed (lower threshold triggers earlier)
				threshold: 0.5,
			}
		);

		if (observerRef.current) {
			observer.observe(observerRef.current);
		}

		return () => observer.disconnect();
	}, [hasMore]);

	return (
		<div style={{
			textAlign: "center",
			maxWidth: "600px",
			margin: "auto",
		}}>
			<h2>Infinite Scroll</h2>
			<ul>
				{items.map((item) => (
					<li key={item.id}>{item.title}</li>
				))}
			</ul>
			<div ref={observerRef} style={{height: "20px"}}></div>
			{loading && <p>Loading more items...</p>}
			{!hasMore && <p>No more items to load.</p>}
		</div>
	);
};

export default InfiniteScroll;

 

How It Works:

  1. useState stores items, page number, and loading state.
  2. useEffect fetches paginated data whenever page updates.
  3. Intersection Observer detects when the user reaches the bottom.
    • When the last item enters the viewport, page increments, triggering a new fetch.
  4. New data is appended to the existing list without losing previous items.

Why This Approach?

  • Efficient scrolling with no manual button clicks.
  • Prevents unnecessary re-renders by tracking the last item.
  • Scales well with large datasets.
7.

Autocomplete Search (useState & API)

Answer
  • Implement an autocomplete search that fetches and displays matching results dynamically.

Autocomplete Search (useState & API)

This component implements an autocomplete search that fetches and displays matching results dynamically as the user types.

  • Uses useState for managing input and results.
  • Uses useEffect for fetching API data dynamically.
  • Implements debouncing to prevent excessive API requests.
  • Handles loading and error states for better UX.

1. Implement the Autocomplete Search Component

 

import { useState, useEffect } from "react";

const AutocompleteSearch = () => {
const [query, setQuery] = useState(""); // Stores user input
const [results, setResults] = useState([]); // Stores fetched results
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [debouncedQuery, setDebouncedQuery] = useState("");

// Debounce effect to reduce API calls
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedQuery(query);
}, 500); // Adjust debounce delay

return () => clearTimeout(timer);
}, [query]);

// Fetch results when debounced query updates
useEffect(() => {
if (!debouncedQuery) {
setResults([]); // Clear results if input is empty
return;
}

const fetchResults = async () => {
setLoading(true);
try {
const response = await fetch(
`https://jsonplaceholder.typicode.com/users?q=${debouncedQuery}`
);
if (!response.ok) throw new Error("Failed to fetch data");
const data = await response.json();
setResults(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};

fetchResults();
}, [debouncedQuery]); // Runs when debounced input updates

return (
<div style={{ textAlign: "center", maxWidth: "400px", margin: "auto" }}>
<h2>Autocomplete Search</h2>
<input
type="text"
placeholder="Search users..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
{loading && <p>Loading...</p>}
{error && <p style={{ color: "red" }}>{error}</p>}
<ul>
{results.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
};

export default AutocompleteSearch;

 

How It Works:

  1. Debouncing:
    • setTimeout waits 500ms before updating the debouncedQuery state.
    • If the user types again before 500ms, the timer resets.
    • This prevents excessive API requests.
  2. Fetching Data:
    • Runs when debouncedQuery updates.
    • Fetches results from an API based on user input.
    • Clears results if input is empty.
  3. Error & Loading Handling:
    • Displays “Loading…” while fetching.
    • Shows error messages if the API request fails.

Why This Approach?

  • Efficient API usage with debounced requests.
  • Prevents unnecessary re-fetching on every keystroke.
  • Improves user experience with error handling & loading states
8.

Cache API Responses (useState & useEffect)

Answer
  • Cache API responses using local state to prevent redundant network requests.

Cache API Responses (useState & useEffect)

This component caches API responses in local state, preventing redundant network requests when fetching the same data.

  • Uses useState to store API responses and manage state.
  • Uses useEffect to trigger API calls when needed.
  • Stores previously fetched results in local state cache.
  • Displays loading and error states for better user experience.

1. Implement the Cached API Fetching Component

 

import { useState, useEffect } from "react";

const CachedFetch = () => {
const [query, setQuery] = useState(""); // User input for search
const [results, setResults] = useState([]); // Stores API results
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [cache, setCache] = useState({}); // Local state cache

useEffect(() => {
if (!query) {
setResults([]);
return;
}

// Check if query exists in cache
if (cache[query]) {
setResults(cache[query]); // Use cached data
return;
}

const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(
`https://jsonplaceholder.typicode.com/users?q=${query}`
);
if (!response.ok) throw new Error("Failed to fetch data");
const data = await response.json();

setResults(data);
setCache((prevCache) => ({ ...prevCache, [query]: data })); // Store in cache
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};

fetchData();
}, [query]); // Runs when query changes

return (
<div style={{ textAlign: "center", maxWidth: "400px", margin: "auto" }}>
<h2>Cached API Fetch</h2>
<input
type="text"
placeholder="Search users..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
{loading && <p>Loading...</p>}
{error && <p style={{ color: "red" }}>{error}</p>}
<ul>
{results.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
};

export default CachedFetch;

 

How It Works:

  1. Checks Cache Before Fetching:
    • If the query exists in cache, it returns stored results instead of fetching.
  2. Fetches Data Only When Needed:
    • If no cache is found, performs API request.
    • Stores response in cache for future use.
  3. Efficient State Handling:
    • Displays loading while fetching.
    • Handles errors gracefully.
    • Clears results when input is empty.

Why This Approach?

  • Reduces redundant API calls by caching data locally.
  • Improves performance with instant cached results.
  • Provides a smooth user experience with loading/error handling.
9.

Protected Routes (React Router)

Answer
  • Implement a protected route component that restricts access to authenticated users.

Protected Routes (React Router)

This implementation ensures that only authenticated users can access certain routes in a React app using React Router.

  • Uses React Router v6 for routing.
  • Uses useContext to manage authentication state.
  • Redirects unauthenticated users to the login page.

1. Set Up Authentication Context

Create an AuthContext to manage authentication state.

 

import { createContext, useContext, useState } from "react";

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);

const login = () => setIsAuthenticated(true);
const logout = () => setIsAuthenticated(false);

return (
<AuthContext.Provider value={{ isAuthenticated, login, logout }}>
{children}
</AuthContext.Provider>
);
};

export const useAuth = () => useContext(AuthContext);

 

2. Create a Protected Route Component

Redirects users to login if they are not authenticated.

 

import { Navigate } from "react-router-dom";
import { useAuth } from "./AuthContext";

const ProtectedRoute = ({ element }) => {
const { isAuthenticated } = useAuth();
return isAuthenticated ? element : <Navigate to="/login" />;
};

export default ProtectedRoute;

 

3. Define Routes in App.js

Wrap the protected page with ProtectedRoute.

 

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { AuthProvider } from "./AuthContext";
import LoginPage from "./LoginPage";
import Dashboard from "./Dashboard";
import ProtectedRoute from "./ProtectedRoute";

const App = () => (
<AuthProvider>
<Router>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/dashboard" element={<ProtectedRoute element={<Dashboard />} />} />
</Routes>
</Router>
</AuthProvider>
);

export default App;

 

4. Implement Login & Logout

Simulate user authentication with a Login button.

 

import { useAuth } from "./AuthContext";
import { useNavigate } from "react-router-dom";

const LoginPage = () => {
const { login } = useAuth();
const navigate = useNavigate();

const handleLogin = () => {
login();
navigate("/dashboard"); // Redirect after login
};

return (
<div style={{ textAlign: "center" }}>
<h2>Login Page</h2>
<button onClick={handleLogin}>Login</button>
</div>
);
};

export default LoginPage;

 

5. Dashboard (Protected Page)

This page is only accessible when logged in.

 

import { useAuth } from "./AuthContext";
import { useNavigate } from "react-router-dom";

const Dashboard = () => {
const { logout } = useAuth();
const navigate = useNavigate();

const handleLogout = () => {
logout();
navigate("/login");
};

return (
<div style={{ textAlign: "center" }}>
<h2>Welcome to Dashboard</h2>
<button onClick={handleLogout}>Logout</button>
</div>
);
};

export default Dashboard;

 

How It Works:

  1. AuthContext stores the authentication state.
  2. ProtectedRoute checks authentication before rendering the protected page.
  3. Unauthenticated users are redirected to the login page.
  4. Login/Logout buttons update the auth state and navigate accordingly.

Why This Approach?

  • Reusable ProtectedRoute component for secure pages.
  • Scalable Authentication Context for managing state globally.
  • Smooth user navigation with useNavigate().
10.

Dynamic Routing (React Router)

Answer
  • Create a user profile page that dynamically loads data based on the URL parameter (/user/:id).

Protected Routes (React Router)

This implementation ensures that only authenticated users can access certain routes in a React app using React Router.

  • Uses React Router v6 for routing.
  • Uses useContext to manage authentication state.
  • Redirects unauthenticated users to the login page.

1. Set Up Authentication Context

Create an AuthContext to manage authentication state.

 

import { createContext, useContext, useState } from "react";

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);

const login = () => setIsAuthenticated(true);
const logout = () => setIsAuthenticated(false);

return (
<AuthContext.Provider value={{ isAuthenticated, login, logout }}>
{children}
</AuthContext.Provider>
);
};

export const useAuth = () => useContext(AuthContext);

 

2. Create a Protected Route Component

Redirects users to login if they are not authenticated.

 

import { Navigate } from "react-router-dom";
import { useAuth } from "./AuthContext";

const ProtectedRoute = ({ element }) => {
const { isAuthenticated } = useAuth();
return isAuthenticated ? element : <Navigate to="/login" />;
};

export default ProtectedRoute;

 

3. Define Routes in App.js

Wrap the protected page with ProtectedRoute.

 

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { AuthProvider } from "./AuthContext";
import LoginPage from "./LoginPage";
import Dashboard from "./Dashboard";
import ProtectedRoute from "./ProtectedRoute";

const App = () => (
<AuthProvider>
<Router>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/dashboard" element={<ProtectedRoute element={<Dashboard />} />} />
</Routes>
</Router>
</AuthProvider>
);

export default App;

 

4. Implement Login & Logout

Simulate user authentication with a Login button.

 

import { useAuth } from "./AuthContext";
import { useNavigate } from "react-router-dom";

const LoginPage = () => {
const { login } = useAuth();
const navigate = useNavigate();

const handleLogin = () => {
login();
navigate("/dashboard"); // Redirect after login
};

return (
<div style={{ textAlign: "center" }}>
<h2>Login Page</h2>
<button onClick={handleLogin}>Login</button>
</div>
);
};

export default LoginPage;

 

5. Dashboard (Protected Page)

This page is only accessible when logged in.

 

import { useAuth } from "./AuthContext";
import { useNavigate } from "react-router-dom";

const Dashboard = () => {
const { logout } = useAuth();
const navigate = useNavigate();

const handleLogout = () => {
logout();
navigate("/login");
};

return (
<div style={{ textAlign: "center" }}>
<h2>Welcome to Dashboard</h2>
<button onClick={handleLogout}>Logout</button>
</div>
);
};

export default Dashboard;

 

How It Works:

  1. AuthContext stores the authentication state.
  2. ProtectedRoute checks authentication before rendering the protected page.
  3. Unauthenticated users are redirected to the login page.
  4. Login/Logout buttons update the auth state and navigate accordingly.

Why This Approach?

  • Reusable ProtectedRoute component for secure pages.
  • Scalable Authentication Context for managing state globally.
  • Smooth user navigation with useNavigate().
11.

Breadcrumb Navigation (useLocation)

Answer
  • Build a breadcrumb navigation system based on the current route.

Breadcrumb Navigation (useLocation)

This component dynamically generates breadcrumb navigation based on the current route using React Router’s useLocation.

  • Uses useLocation to get the current path.
  • Dynamically creates breadcrumb links based on the path.
  • Uses React Router’s Link for navigation.

1. Implement the Breadcrumb Component (this doesn’t handle cases with query parameters, path parameters, etc.)

 

import { Link, useLocation } from "react-router-dom";

const Breadcrumbs = () => {
const location = useLocation();
const pathnames = location.pathname.split("/").filter((path) => path);

return (
<nav style={{ padding: "10px" }}>
<Link to="/">Home</Link>
{pathnames.map((path, index) => {
const routeTo = `/${pathnames.slice(0, index + 1).join("/")}`;
return (
<span key={routeTo}>
{" > "}
<Link to={routeTo}>{path}</Link>
</span>
);
})}
</nav>
);
};

export default Breadcrumbs;

 

2. Use Breadcrumbs in Your App

Place the <Breadcrumbs /> component inside the main layout.

 

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Breadcrumbs from "./Breadcrumbs";
import HomePage from "./HomePage";
import Products from "./Products";
import ProductDetails from "./ProductDetails";

const App = () => {
return (
<Router>
<Breadcrumbs />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:id" element={<ProductDetails />} />
</Routes>
</Router>
);
};

export default App;

 

3. Example Pages for Navigation

HomePage.js

 

const HomePage = () => <h2>Home Page</h2>;
export default HomePage;

 

Products.js

 

import { Link } from "react-router-dom";

const Products = () => (
<div>
<h2>Products Page</h2>
<Link to="/products/1">Go to Product 1</Link>
</div>
);

export default Products;

 

ProductDetails.js

import { useParams } from "react-router-dom";

const ProductDetails = () => {
const { id } = useParams();
return <h2>Product Details for Product {id}</h2>;
};

export default ProductDetails;

 

How It Works:

  1. useLocation extracts the current path.
  2. Splits the path into segments, creating breadcrumb links dynamically.
  3. Each segment is clickable, allowing navigation to previous pages.
  4. Automatically updates when the user navigates.

Why This Approach?

  • Dynamic Breadcrumbs – Works for any route structure.
  • Reusable Component – Can be used across the app.
  • Enhances UX – Provides clear navigation history.
12.

Scroll Restoration on Route Change (useEffect)

Answer
  • Ensure the page scrolls to the top when navigating between routes.

Scroll Restoration on Route Change (useEffect)

This implementation ensures that the page automatically scrolls to the top when the user navigates between different routes using React Router.

  • Uses useEffect to listen for route changes.
  • Uses useLocation from React Router to detect navigation.
  • Improves user experience by preventing unwanted scroll positions.

1. Implement the Scroll Restoration Component

 

import { useEffect } from "react";
import { useLocation } from "react-router-dom";

const ScrollToTop = () => {
const { pathname } = useLocation();

useEffect(() => {
window.scrollTo(0, 0); // Scroll to top on route change
}, [pathname]);

return null; // This component doesn't render anything
};

export default ScrollToTop;

 

2. Use the Component in App.js

Place <ScrollToTop /> inside <Router> so it runs on every route change.

 

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import ScrollToTop from "./ScrollToTop";
import HomePage from "./HomePage";
import AboutPage from "./AboutPage";
import ContactPage from "./ContactPage";

const App = () => {
return (
<Router>
<ScrollToTop /> {/* Ensures scroll resets on route change */}
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/contact" element={<ContactPage />} />
</Routes>
</Router>
);
};

export default App;

 

3. Example Pages for Navigation

HomePage.js

 

const HomePage = () => (
<div>
<h2>Home Page</h2>
<p>Scroll down, then navigate to another page.</p>
</div>
);

export default HomePage;

 

AboutPage.js

 

const AboutPage = () => (
<div>
<h2>About Page</h2>
<p>This page should always start from the top when loaded.</p>
</div>
);

export default AboutPage;

 

ContactPage.js

 

const ContactPage = () => (
<div>
<h2>Contact Page</h2>
<p>Ensure the page starts from the top on navigation.</p>
</div>
);

export default ContactPage;

 

How It Works:

  1. useLocation detects route changes.
  2. useEffect triggers window.scrollTo(0, 0) whenever the route changes.
  3. Ensures a smooth user experience, preventing unwanted scroll positions.

Why This Approach?

  • Lightweight & efficient – Uses built-in browser scrolling.
  • Reusable Component – Works across the entire app.
  • Improves UX – Avoids unwanted scroll positions when navigating.
13.

Sidebar Navigation with Active Links

Answer

Sidebar Navigation with Active Links (Using useLocation)

This implementation creates a sidebar navigation where the active link is highlighted based on the current route using React Router’s useLocation.

  • Uses useLocation to detect the active route dynamically.
  • Highlights the active link by comparing the current pathname.
  • Uses CSS styles to differentiate active and inactive links.

1. Implement the Sidebar Component

 

import { Link, useLocation } from "react-router-dom";
import "./Sidebar.css"; // Import styles

const Sidebar = () => {
const location = useLocation(); // Get current route

return (
<nav className="sidebar">
<Link to="/" className={location.pathname === "/" ? "active-link" : ""}>
Home
</Link>
<Link to="/about" className={location.pathname === "/about" ? "active-link" : ""}>
About
</Link>
<Link to="/services" className={location.pathname === "/services" ? "active-link" : ""}>
Services
</Link>
<Link to="/contact" className={location.pathname === "/contact" ? "active-link" : ""}>
Contact
</Link>
</nav>
);
};

export default Sidebar;

 

2. Sidebar Styles (Sidebar.css)

 

.sidebar {
width: 200px;
height: 100vh;
background: #f4f4f4;
padding: 20px;
display: flex;
flex-direction: column;
gap: 10px;
}

.sidebar a {
text-decoration: none;
color: #333;
padding: 10px;
display: block;
border-radius: 5px;
transition: background 0.3s ease;
}

.sidebar a:hover {
background: #ddd;
}

.active-link {
background: #007bff;
color: white;
font-weight: bold;
}

 

3. Use the Sidebar in App.js

 

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Sidebar from "./Sidebar";
import Home from "./Home";
import About from "./About";
import Services from "./Services";
import Contact from "./Contact";

const App = () => {
return (
<Router>
<div style={{ display: "flex" }}>
<Sidebar />
<div style={{ padding: "20px", flex: 1 }}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/services" element={<Services />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</div>
</div>
</Router>
);
};

export default App;

 

 

4. Example Pages

Home.js

 

const Home = () => <h2>Home Page</h2>;
export default Home;

 

About.js

 

const About = () => <h2>About Page</h2>;
export default About;

 

Services.js

 

const Services = () => <h2>Services Page</h2>;
export default Services;

 

Contact.js

 

const Contact = () => <h2>Contact Page</h2>;
export default Contact;

 

How It Works:

  1. useLocation retrieves the current pathname, allowing us to dynamically check which link should be highlighted.
  2. Link elements use conditional class assignment, applying "active-link" only when the pathname matches the link’s destination.
  3. CSS highlights the active link, ensuring a visual indication of the current page.

Why This Approach?

  • Directly uses useLocation to detect active routes, ensuring accuracy.
  • More control over styling and logic, compared to NavLink.
  • Easy to extend by adding more links, icons, or animations.
14.

Reusable Form Input Component (useState)

Answer
  • Create a reusable input component that supports validation and custom error messages.

Reusable Form Input Component (useState)

This implementation creates a reusable input component that:

    • Supports custom validation and error messages.
  • Uses useState to manage input state.
  • Works with different input types (text, email, password, etc.).

1. Implement the Reusable Input Component

 

import { useState } from "react";

const InputField = ({ label, type, name, value, onChange, validate, errorMessage }) => {
const [error, setError] = useState("");

const handleBlur = () => {
if (validate && !validate(value)) {
setError(errorMessage);
} else {
setError("");
}
};

return (
<div style={{ marginBottom: "15px" }}>
<label>{label}</label>
<input
type={type}
name={name}
value={value}
onChange={onChange}
onBlur={handleBlur}
style={{ display: "block", padding: "8px", width: "100%" }}
/>
{error && <p style={{ color: "red", fontSize: "12px" }}>{error}</p>}
</div>
);
};

export default InputField;

 

2. Use the Reusable Input Component in a Form

 

import { useState } from "react";
import InputField from "./InputField";

const validateEmail = (email) => /\S+@\S+\.\S+/.test(email);
const validatePassword = (password) => password.length >= 6;

const FormExample = () => {
const [formData, setFormData] = useState({ email: "", password: "" });

const handleChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};

const handleSubmit = (e) => {
e.preventDefault();
alert("Form submitted successfully!");
};

return (
<form onSubmit={handleSubmit} style={{ maxWidth: "400px", margin: "auto" }}>
<h2>Reusable Input Form</h2>

<InputField
label="Email"
type="email"
name="email"
value={formData.email}
onChange={handleChange}
validate={validateEmail}
errorMessage="Invalid email format"
/>

<InputField
label="Password"
type="password"
name="password"
value={formData.password}
onChange={handleChange}
validate={validatePassword}
errorMessage="Password must be at least 6 characters"
/>

<button type="submit">Submit</button>
</form>
);
};

export default FormExample;

 

How It Works:

  1. ReusabilityInputField works for any input type with validation.
  2. Validation on Blur – Shows an error only when the user moves away from the field.
  3. Dynamic Error Messages – Displays custom error messages based on validation rules.
  4. Easy Form Integration – Can be used in any form with different input fields.

Why This Approach?

  • Encapsulates validation logic inside a reusable component.
  • Supports different validation rules (email, password, etc.).
  • Keeps the form clean by avoiding duplicate validation logic.
15.

File Upload with Preview

Answer
  • Allow users to upload an image file and display a preview before submission.

File Upload with Preview

This implementation allows users to upload an image file and preview it before submission.

  • Uses useState to manage file and preview state.
  • Supports image validation (only allows images).
  • Displays image preview before submission.

1. Implement the File Upload Component

 

import { useState } from "react";

const FileUpload = () => {
const [file, setFile] = useState(null);
const [preview, setPreview] = useState(null);
const [error, setError] = useState("");

const handleFileChange = (event) => {
const selectedFile = event.target.files[0];

if (selectedFile) {
if (!selectedFile.type.startsWith("image/")) {
setError("Only image files are allowed");
setFile(null);
setPreview(null);
return;
}

setError("");
setFile(selectedFile);
setPreview(URL.createObjectURL(selectedFile));
}
};

const handleSubmit = (event) => {
event.preventDefault();
if (!file) {
setError("Please upload an image before submitting.");
return;
}
alert("File uploaded successfully!");
};

return (
<div style={{ textAlign: "center", maxWidth: "400px", margin: "auto" }}>
<h2>File Upload with Preview</h2>
<input type="file" accept="image/*" onChange={handleFileChange} />
{error && <p style={{ color: "red" }}>{error}</p>}
{preview && (
<div style={{ marginTop: "15px" }}>
<img
src={preview}
alt="Preview"
style={{ width: "100%", maxWidth: "300px", borderRadius: "5px" }}
/>
</div>
)}
<button onClick={handleSubmit} style={{ marginTop: "10px" }}>
Upload
</button>
</div>
);
};

export default FileUpload;

 

How It Works:

  1. User selects a file – Updates file and generates a preview URL.
  2. File validation – Ensures only image files are allowed.
  3. Displays image preview – Shows the uploaded file before submission.
  4. Handles errors – Alerts if no file is selected or an invalid file is chosen.

Why This Approach?

  • Prevents non-image uploads with validation.
  • Provides instant feedback with a preview before submission.
  • Keeps the form interactive and user-friendly.
16.

Controlled vs. Uncontrolled Forms

Answer
  • Build a form with both controlled and uncontrolled inputs and compare their behavior.

Controlled vs. Uncontrolled Forms in React

This implementation demonstrates the difference between controlled and uncontrolled form inputs by creating a simple form with both types of inputs.

  • Controlled inputs – Managed by React using useState.
  • Uncontrolled inputs – Uses Refs to access DOM values directly.
  • Comparison – Shows how both approaches handle form data.

1. Implement the Controlled vs. Uncontrolled Form Component

 

import { useState, useRef } from "react";

const ControlledVsUncontrolled = () => {
// Controlled Input (Managed by React State)
const [controlledValue, setControlledValue] = useState("");

// Uncontrolled Input (Uses Ref)
const uncontrolledRef = useRef(null);

// Handle Controlled Input Change
const handleControlledChange = (event) => {
setControlledValue(event.target.value);
};

// Handle Form Submission
const handleSubmit = (event) => {
event.preventDefault();
alert(
`Controlled Input: ${controlledValue}\nUncontrolled Input: ${uncontrolledRef.current.value}`
);
};

return (
<div style={{ textAlign: "center", maxWidth: "400px", margin: "auto" }}>
<h2>Controlled vs. Uncontrolled Inputs</h2>
<form onSubmit={handleSubmit}>
{/* Controlled Input */}
<div>
<label>Controlled Input: </label>
<input
type="text"
value={controlledValue}
onChange={handleControlledChange}
/>
</div>

{/* Uncontrolled Input */}
<div style={{ marginTop: "10px" }}>
<label>Uncontrolled Input: </label>
<input type="text" ref={uncontrolledRef} />
</div>

<button type="submit" style={{ marginTop: "15px" }}>Submit</button>
</form>
</div>
);
};

export default ControlledVsUncontrolled;

 

How It Works:

  1. Controlled Input
    • Uses useState to manage the value.
    • React controls the input’s value.
  2. Uncontrolled Input
    • Uses useRef to access the value directly from the DOM.
    • The browser manages the input instead of React.
  3. Form Submission
    • Displays both controlled and uncontrolled input values on submit.

 

Comparison Table

 

Feature Controlled Input (useState) Uncontrolled Input (useRef)
Managed By React State DOM (Direct Access via Ref)
Re-rendering Triggers re-renders on change Does not trigger re-renders
Validation Can be validated in real-time Requires manual validation
Use Case Forms that need validation & dynamic state updates Simple forms with minimal interaction

 

Why This Approach?

  • Demonstrates both approaches side by side for easy comparison.
  • Shows pros & cons of each method with practical use cases.
  • Helps developers decide which approach suits their needs.
17.

Multi-Select Dropdown (useState)

Answer
  • Implement a multi-select dropdown where users can select multiple items.

Multi-Select Dropdown (useState)

This implementation creates a multi-select dropdown where users can select multiple items from a list.

  • Uses useState to manage selected options.
  • Allows multiple selections and displays selected items.
  • Supports deselection by clicking on selected options.

1. Implement the Multi-Select Dropdown Component

 

import { useState } from "react";

const MultiSelectDropdown = () => {
const options = ["Apple", "Banana", "Cherry", "Mango", "Orange", "Grapes"];
const [selectedItems, setSelectedItems] = useState([]);

// Handle selection & deselection
const handleSelect = (item) => {
if (selectedItems.includes(item)) {
setSelectedItems(selectedItems.filter((selected) => selected !== item)); // Remove item
} else {
setSelectedItems([...selectedItems, item]); // Add item
}
};

return (
<div style={{ textAlign: "center", maxWidth: "400px", margin: "auto" }}>
<h2>Multi-Select Dropdown</h2>
<div style={{ border: "1px solid #ccc", padding: "10px", borderRadius: "5px" }}>
<p>Select Fruits:</p>
{options.map((item) => (
<div key={item}>
<label>
<input
type="checkbox"
checked={selectedItems.includes(item)}
onChange={() => handleSelect(item)}
/>
{item}
</label>
</div>
))}
</div>

{/* Display selected items */}
<div style={{ marginTop: "10px" }}>
<strong>Selected:</strong> {selectedItems.length > 0 ? selectedItems.join(", ") : "None"}
</div>
</div>
);
};

export default MultiSelectDropdown;

 

How It Works:

  1. State Management (useState)
    • selectedItems stores the selected options.
    • Clicking an item adds or removes it from the selection.
  2. Dynamic Selection & Deselection
    • Uses checkboxes to allow multiple selections.
    • Clicking a selected item unchecks it (removes it).
  3. Displaying Selected Items
    • Shows the selected items as a comma-separated list.

Why This Approach?

  • Simple and scalable – Easy to expand with more items.
  • Allows real-time updates – Users see changes instantly.
  • Enhances usability – Provides a clear, interactive UI.
18.

Optimize a Search Component (useMemo, useCallback,

useDeferred)

Answer

Optimize a Search Component (useMemo, useCallback, useDeferredValue)

This implementation improves the performance of a search component by:

  • Using useMemo to prevent unnecessary filtering re-renders.
  • Using useCallback to memoize event handlers.
  • Using useDeferredValue to avoid blocking UI updates during typing.

1. Implement the Optimized Search Component

 

import { useState, useMemo, useCallback, useDeferredValue } from "react";

// Sample data for searching
const items = [
"Apple", "Banana", "Cherry", "Mango", "Orange", "Grapes",
"Pineapple", "Strawberry", "Blueberry", "Peach", "Watermelon"
];

const SearchComponent = () => {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query); // Defers search input processing

// Memoize filtered results
const filteredItems = useMemo(() => {
return items.filter((item) =>
item.toLowerCase().includes(deferredQuery.toLowerCase())
);
}, [deferredQuery]);

// Memoize the input change handler
const handleChange = useCallback((event) => {
setQuery(event.target.value);
}, []);

return (
<div style={{ textAlign: "center", maxWidth: "400px", margin: "auto" }}>
<h2>Optimized Search</h2>
<input
type="text"
placeholder="Search..."
value={query}
onChange={handleChange}
/>
<ul>
{filteredItems.length > 0 ? (
filteredItems.map((item, index) => <li key={index}>{item}</li>)
) : (
<li>No results found</li>
)}
</ul>
</div>
);
};

export default SearchComponent;

 

How It Works:

  1. useMemo Optimizes Filtering
    • Prevents unnecessary recalculations when query hasn’t changed.
    • Improves performance, especially for large datasets.
  2. useCallback Optimizes Event Handler
    • Prevents re-creating the handleChange function on every render.
    • Useful in components that receive functions as props.
  3. useDeferredValue Improves UI Responsiveness
    • Allows the UI (e.g., typing in the search box) to remain smooth.
    • Delays filtering computation to prevent blocking the main thread.

Why This Approach?

  • Prevents unnecessary re-renders and computations.
  • Ensures smooth UI interactions while searching.
  • Scales well for large lists and real-time filtering.
19.

Prevent Unnecessary Re-Renders (React.memo)

Answer
  • Create a component that only re-renders when necessary, using React.memo.

Prevent Unnecessary Re-Renders (React.memo)

This implementation optimizes a React component by using React.memo to prevent unnecessary re-renders.

  • Uses React.memo to prevent re-renders if props don’t change.
  • Demonstrates unnecessary vs optimized renders.
  • Improves performance when dealing with frequently updated states.

1. Implement the Optimized Component Using React.memo

 

import { useState, memo } from "react";

// Child Component (Optimized with React.memo)
const ChildComponent = memo(({ count }) => {
console.log("ChildComponent Rendered");
return <h3>Count: {count}</h3>;
});

const ParentComponent = () => {
const [count, setCount] = useState(0);
const [randomValue, setRandomValue] = useState(0); // Irrelevant state

return (
<div style={{ textAlign: "center", maxWidth: "400px", margin: "auto" }}>
<h2>Prevent Unnecessary Re-Renders</h2>
<ChildComponent count={count} /> {/* Only re-renders when 'count' changes */}

<button onClick={() => setCount(count + 1)}>Increment Count</button>
<button onClick={() => setRandomValue(Math.random())}>Update Random Value</button>
</div>
);
};

export default ParentComponent;

 

How It Works:

  1. React.memo(ChildComponent)
    • Prevents ChildComponent from re-rendering unless its props change.
    • If only randomValue updates, ChildComponent does not re-render.
  2. Parent Updates Irrelevant State (randomValue)
    • Updates that don’t affect the ChildComponent won’t cause a re-render.
    • Console logs confirm that ChildComponent only renders when count changes.

Why This Approach?

  • Optimizes performance by avoiding unnecessary updates.
  • Ensures components re-render only when needed.
  • Useful for complex UI elements (e.g., large lists, expensive calculations).
20.

Lazy Loading Components (React.lazy & Suspense)

Answer
  • Implement lazy loading for a heavy component to improve performance.

Lazy Loading Components (React.lazy & Suspense)

This implementation uses React.lazy and Suspense to dynamically load a heavy component, improving performance by reducing the initial bundle size.

  • Uses React.lazy to defer loading until needed.
  • Wraps in Suspense to display a fallback while loading.
  • Improves performance by loading components only when necessary.

1. Create a Heavy Component (HeavyComponent.js)

This simulates a large component that should be loaded lazily.

 

const HeavyComponent = () => {
return (
<div style={{ textAlign: "center", marginTop: "20px" }}>
<h2>Heavy Component Loaded</h2>
<p>This component was lazily loaded to improve performance.</p>
</div>
);
};

export default HeavyComponent;

 

2. Lazy Load the Component in App.js

 

import React, { lazy, Suspense, useState } from "react";

// Lazy load the HeavyComponent
const HeavyComponent = lazy(() => import("./HeavyComponent"));

const App = () => {
const [showComponent, setShowComponent] = useState(false);

return (
<div style={{ textAlign: "center", marginTop: "20px" }}>
<h2>Lazy Loading Example</h2>
<button onClick={() => setShowComponent(true)}>Load Component</button>

{/* Show the lazy-loaded component only when needed */}
{showComponent && (
<Suspense fallback={<p>Loading Component...</p>}>
<HeavyComponent />
</Suspense>
)}
</div>
);
};

export default App;

 

How It Works:

  1. Lazy Loading (React.lazy)
    • Defers loading HeavyComponent until needed.
  2. Fallback Handling (Suspense)
    • Displays "Loading Component..." while waiting for lazy-loaded content.
  3. Loads Component on Button Click
    • The component loads only when the user triggers it, reducing initial load time.

Why This Approach?

  • Reduces initial bundle size and speeds up app load time.
  • Efficiently loads large components only when needed.
  • Improves user experience by displaying a loading message.
21.

WebSocket Integration (useEffect)

Answer
  • Implement a real-time chat using WebSockets (wss://echo.websocket.org).

WebSocket Integration (useEffect) – Real-Time Chat

This implementation sets up a real-time chat system using WebSockets with wss://echo.websocket.org.

  • Uses WebSocket API for real-time messaging.
  • Uses useState to manage messages.
  • Uses useEffect to maintain WebSocket connection.
  • Supports sending and receiving messages dynamically.

1. Implement the WebSocket Chat Component

 

import { useState, useEffect, useRef } from "react";

const WebSocketChat = () => {
const [messages, setMessages] = useState([]); // Stores chat messages
const [input, setInput] = useState(""); // Stores user input
const [status, setStatus] = useState("Connecting...");
const ws = useRef(null); // WebSocket reference

useEffect(() => {
// Initialize WebSocket connection
ws.current = new WebSocket("wss://echo.websocket.org");

ws.current.onopen = () => setStatus("Connected");
ws.current.onclose = () => setStatus("Disconnected");
ws.current.onerror = () => setStatus("Error occurred");

ws.current.onmessage = (event) => {
setMessages((prev) => [...prev, { text: event.data, sender: "Server" }]);
};

return () => {
ws.current.close(); // Cleanup WebSocket on component unmount
};
}, []);

const sendMessage = () => {
if (input.trim() && ws.current.readyState === WebSocket.OPEN) {
ws.current.send(input);
setMessages((prev) => [...prev, { text: input, sender: "You" }]);
setInput(""); // Clear input field
}
};

return (
<div style={{ textAlign: "center", maxWidth: "400px", margin: "auto" }}>
<h2>WebSocket Chat</h2>
<p>Status: {status}</p>

<div style={{
border: "1px solid #ccc",
padding: "10px",
height: "200px",
overflowY: "auto",
marginBottom: "10px",
}}>
{messages.map((msg, index) => (
<p key={index} style={{ textAlign: msg.sender === "You" ? "right" : "left" }}>
<strong>{msg.sender}:</strong> {msg.text}
</p>
))}
</div>

<input
type="text"
value={input}
placeholder="Type a message..."
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && sendMessage()}
/>
<button onClick={sendMessage} style={{ marginLeft: "5px" }}>Send</button>
</div>
);
};

export default WebSocketChat;

 

How It Works:

  1. useRef stores WebSocket connection to persist across renders.
  2. useEffect initializes the WebSocket and cleans up on unmount.
  3. Handles WebSocket events (onopen, onclose, onmessage).
  4. Stores messages in state and updates dynamically.
  5. Users can send messages, and the server echoes them back in real-time.

Why This Approach?

  • Provides real-time communication with WebSockets.
  • Manages connection state dynamically (Connected, Disconnected).
  • Interactive UI with chat history and input field.
22.

Optimized Image Gallery (Intersection Observer)

Answer
  • Load images only when they are visible using the Intersection Observer API.

Optimized Image Gallery (Intersection Observer)

This implementation loads images only when they are visible using the Intersection Observer API, improving performance by reducing unnecessary image loading.

  • Uses useState to store image data.
  • Uses useEffect and Intersection Observer to detect when an image is in view.
  • Prevents unnecessary network requests by loading images only when needed.

1. Implement the Optimized Image Gallery Component

 

import { useState, useEffect, useRef } from "react";

// Sample image data
const imageUrls = [
"https://via.placeholder.com/300?text=Image+1",
"https://via.placeholder.com/300?text=Image+2",
"https://via.placeholder.com/300?text=Image+3",
"https://via.placeholder.com/300?text=Image+4",
"https://via.placeholder.com/300?text=Image+5",
"https://via.placeholder.com/300?text=Image+6",
"https://via.placeholder.com/300?text=Image+7",
"https://via.placeholder.com/300?text=Image+8",
"https://via.placeholder.com/300?text=Image+9",
];

const LazyImage = ({ src, alt }) => {
const [isVisible, setIsVisible] = useState(false);
const imgRef = useRef(null);

useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
setIsVisible(true);
observer.disconnect(); // Stop observing once the image is loaded
}
},
{ threshold: 0.5 }
);

if (imgRef.current) {
observer.observe(imgRef.current);
}

return () => observer.disconnect();
}, []);

return (
<div ref={imgRef} style={{ minHeight: "300px", display: "flex", alignItems: "center", justifyContent: "center" }}>
{isVisible ? <img src={src} alt={alt} style={{ width: "100%", height: "auto" }} /> : <p>Loading...</p>}
</div>
);
};

const ImageGallery = () => {
return (
<div style={{ textAlign: "center", maxWidth: "600px", margin: "auto" }}>
<h2>Optimized Image Gallery</h2>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px" }}>
{imageUrls.map((url, index) => (
<LazyImage key={index} src={url} alt={`Image ${index + 1}`} />
))}
</div>
</div>
);
};

export default ImageGallery;

 

How It Works:

  1. Intersection Observer watches images
    • When an image enters the viewport, it loads dynamically.
    • observer.disconnect() stops observing after loading.
  2. Prevents Unnecessary Image Loads
    • Only loads images when they become visible instead of loading all at once.
  3. Fallback UI for Loading State
    • Shows “Loading…” placeholder before the image appears.

Why This Approach?

  • Improves performance by reducing unnecessary image requests.
  • Reduces memory usage by loading only visible images.
  • Enhances UX with smooth lazy-loading.
23.

User Authentication (using Context API)

Answer
  • Use the Context API to manage global user authentication state.

Global State Management (Context API) – User Authentication

This implementation manages global user authentication state using React Context API.

  • Uses createContext for authentication state.
  • Uses useContext to access authentication data anywhere.
  • Supports Login / Logout functionality across the app.
  • Prevents prop drilling by providing global state access.

1. Create the AuthContext for Authentication State

 

import { createContext, useContext, useState } from "react";

// Create AuthContext
const AuthContext = createContext();

// Provide Auth State to the App
export const AuthProvider = ({ children }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);

const login = () => setIsAuthenticated(true);
const logout = () => setIsAuthenticated(false);

return (
<AuthContext.Provider value={{ isAuthenticated, login, logout }}>
{children}
</AuthContext.Provider>
);
};

// Custom Hook to Access Auth Context
export const useAuth = () => useContext(AuthContext);

 

2. Implement Protected Routes Using Context

This component restricts access to authenticated users.

 

import { Navigate } from "react-router-dom";
import { useAuth } from "./AuthContext";

const ProtectedRoute = ({ element }) => {
const { isAuthenticated } = useAuth();
return isAuthenticated ? element : <Navigate to="/login" />;
};

export default ProtectedRoute;

 

3. Implement the Login Page

 

import { useAuth } from "./AuthContext";
import { useNavigate } from "react-router-dom";

const LoginPage = () => {
const { login } = useAuth();
const navigate = useNavigate();

const handleLogin = () => {
login();
navigate("/dashboard"); // Redirect after login
};

return (
<div style={{ textAlign: "center" }}>
<h2>Login Page</h2>
<button onClick={handleLogin}>Login</button>
</div>
);
};

export default LoginPage;

 

4. Implement the Dashboard Page (Protected Page)

 

import { useAuth } from "./AuthContext";
import { useNavigate } from "react-router-dom";

const Dashboard = () => {
const { logout } = useAuth();
const navigate = useNavigate();

const handleLogout = () => {
logout();
navigate("/login");
};

return (
<div style={{ textAlign: "center" }}>
<h2>Welcome to Dashboard</h2>
<button onClick={handleLogout}>Logout</button>
</div>
);
};

export default Dashboard;

 

5. Set Up Routes and Wrap the App with AuthProvider

 

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { AuthProvider } from "./AuthContext";
import LoginPage from "./LoginPage";
import Dashboard from "./Dashboard";
import ProtectedRoute from "./ProtectedRoute";

const App = () => {
return (
<AuthProvider>
<Router>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/dashboard" element={<ProtectedRoute element={<Dashboard />} />} />
</Routes>
</Router>
</AuthProvider>
);
};

export default App;

 

How It Works:

  1. AuthProvider manages the authentication state globally.
  2. useAuth provides access to auth functions (login, logout).
  3. ProtectedRoute restricts access to logged-in users only.
  4. Login updates global state, allowing navigation to the dashboard.
  5. Logout resets authentication state and redirects back to login.

Why This Approach?

  • Removes prop drilling by using a global authentication state.
  • Easily accessible authentication functions (login/logout) across the app.
  • Prevents unauthorized access with protected routes.
24.

Undo/Redo with useReducer

Answer
  • Implement an undo/redo feature for a simple text editor using useReducer.

Undo/Redo with useReducer – Simple Text Editor

This implementation creates a text editor with Undo/Redo functionality using

useReducer to manage state history efficiently.

  • Uses useReducer to track past, present, and future states.
  • Implements Undo / Redo functionality to revert and restore changes.
  • Prevents unnecessary re-renders by handling state updates efficiently.

1. Implement the useReducer Logic for Undo/Redo

 

import { useReducer } from "react";

// Define initial state
const initialState = {
past: [],
present: "",
future: [],
};

// Reducer function to handle state changes
const reducer = (state, action) => {
switch (action.type) {
case "TYPING":
return {
past: [...state.past, state.present],
present: action.payload,
future: [],
};

case "UNDO":
if (state.past.length === 0) return state; // Prevent undo if no history
return {
past: state.past.slice(0, -1),
present: state.past[state.past.length - 1],
future: [state.present, ...state.future],
};

case "REDO":
if (state.future.length === 0) return state; // Prevent redo if no future states
return {
past: [...state.past, state.present],
present: state.future[0],
future: state.future.slice(1),
};

case "RESET":
return initialState;

default:
return state;
}
};

 

2. Implement the Undo/Redo Text Editor Component

 

const UndoRedoTextEditor = () => {
const [state, dispatch] = useReducer(reducer, initialState);

return (
<div style={{ textAlign: "center", maxWidth: "500px", margin: "auto" }}>
<h2>Undo/Redo Text Editor</h2>

<textarea
rows="5"
cols="40"
value={state.present}
onChange={(e) => dispatch({ type: "TYPING", payload: e.target.value })}
placeholder="Type something..."
/>

<div style={{ marginTop: "10px" }}>
<button onClick={() => dispatch({ type: "UNDO" })} disabled={state.past.length === 0}>
Undo
</button>
<button onClick={() => dispatch({ type: "REDO" })} disabled={state.future.length === 0}>
Redo
</button>
<button onClick={() => dispatch({ type: "RESET" })}>
Reset
</button>
</div>
</div>
);
};

export default UndoRedoTextEditor;

 

How It Works:

  1. useReducer stores three states:
    • past – Stores previous values for undo.
    • present – Current text content.
    • future – Stores values for redo.
  2. Typing (TYPING action)
    • Saves the previous value to past[].
    • Updates present with new text.
    • Clears the future[] to prevent invalid redos.
  3. Undo (UNDO action)
    • Moves the last value from past[] to present.
    • Moves the current present to future[] for redo.
  4. Redo (REDO action)
    • Moves the first value from future[] back to present.
    • Restores the state for seamless redo operations.

Why This Approach?

  • Optimized state management using useReducer instead of multiple useState calls.
  • Efficient undo/redo functionality with separate history tracking.
  • Scalable – Can be extended to store multiple field histories.

 

25.

Centralized Notifications System

Answer
  • Create a global notifications system using Context API and useReducer.

Centralized Notifications System (Context API & useReducer)

This implementation creates a global notifications system using Context API and

useReducer, allowing any component to trigger and display notifications.

  • Uses useContext for global access to notifications.
  • Uses useReducer for efficient state management.
  • Supports adding & removing notifications dynamically.
  • Ensures notifications auto-dismiss after a timeout.

1. Create the NotificationContext

Manages notifications globally.

 

import { createContext, useContext, useReducer } from "react";

// Initial state
const initialState = [];

// Reducer function
const notificationReducer = (state, action) => {
switch (action.type) {
case "ADD_NOTIFICATION":
return [...state, action.payload];
case "REMOVE_NOTIFICATION":
return state.filter((notification) => notification.id !== action.payload);
default:
return state;
}
};

// Create context
const NotificationContext = createContext();

// Notification Provider
export const NotificationProvider = ({ children }) => {
const [notifications, dispatch] = useReducer(notificationReducer, initialState);

const addNotification = (message, type = "info", duration = 3000) => {
const id = Date.now();
dispatch({ type: "ADD_NOTIFICATION", payload: { id, message, type } });

// Auto-remove notification after duration
setTimeout(() => {
dispatch({ type: "REMOVE_NOTIFICATION", payload: id });
}, duration);
};

return (
<NotificationContext.Provider value={{ notifications, addNotification }}>
{children}
</NotificationContext.Provider>
);
};

// Custom Hook to Use Notifications
export const useNotifications = () => useContext(NotificationContext);

 

2. Implement the Notification Display Component

 

import { useNotifications } from "./NotificationContext";

const Notifications = () => {
const { notifications } = useNotifications();

return (
<div style={{
position: "fixed", top: "10px", right: "10px", width: "300px", zIndex: 1000
}}>
{notifications.map(({ id, message, type }) => (
<div key={id} style={{
marginBottom: "10px",
padding: "10px",
borderRadius: "5px",
background: type === "error" ? "#ff4d4d" : type === "success" ? "#4caf50" : "#2196f3",
color: "white"
}}>
{message}
</div>
))}
</div>
);
};

export default Notifications;

 

3. Implement a Component to Trigger Notifications

 

import { useNotifications } from "./NotificationContext";

const TestComponent = () => {
const { addNotification } = useNotifications();

return (
<div style={{ textAlign: "center", marginTop: "20px" }}>
<h2>Global Notifications System</h2>
<button onClick={() => addNotification("Info Notification", "info")}>
Show Info
</button>
<button onClick={() => addNotification("Success Notification", "success")}>
Show Success
</button>
<button onClick={() => addNotification("Error Notification", "error")}>
Show Error
</button>
</div>
);
};

export default TestComponent;

 

4. Integrate Everything in App.js

 

import { BrowserRouter as Router } from "react-router-dom";
import { NotificationProvider } from "./NotificationContext";
import Notifications from "./Notifications";
import TestComponent from "./TestComponent";

const App = () => {
return (
<NotificationProvider>
<Router>
<Notifications />
<TestComponent />
</Router>
</NotificationProvider>
);
};

export default App;

 

How It Works:

  1. NotificationContext manages notifications globally using useReducer.
  2. Any component can trigger notifications using useNotifications().
  3. Notifications auto-dismiss after a set duration.
  4. Notifications appear in a fixed corner for better UX.

Why This Approach?

  • Provides a centralized notification system without prop drilling.
  • Efficient state updates using useReducer.
  • Customizable notification types and duration.
26.

Theme Provider with Context API

Answer
  • Implement a global theme provider that supports light/dark mode.

Theme Provider with Context API (Light/Dark Mode)

This implementation manages global theme state using the Context API, allowing any component to toggle between Light and Dark mode.

  • Uses Context API to provide theme state globally.
  • Uses useReducer for efficient state management.
  • Stores theme preference in localStorage for persistence.
  • Applies CSS variables to dynamically change styles.

1. Create the ThemeContext

Manages theme state globally and persists it in localStorage.

 

import { createContext, useContext, useReducer, useEffect } from "react";

// Define theme actions
const TOGGLE_THEME = "TOGGLE_THEME";

// Define the initial theme state
const initialState = {
theme: localStorage.getItem("theme") || "light",
};

// Reducer function for theme toggling
const themeReducer = (state, action) => {
switch (action.type) {
case TOGGLE_THEME:
const newTheme = state.theme === "light" ? "dark" : "light";
localStorage.setItem("theme", newTheme);
return { theme: newTheme };
default:
return state;
}
};

// Create context
const ThemeContext = createContext();

// Theme Provider
export const ThemeProvider = ({ children }) => {
const [state, dispatch] = useReducer(themeReducer, initialState);

useEffect(() => {
document.documentElement.setAttribute("data-theme", state.theme);
}, [state.theme]);

return (
<ThemeContext.Provider value={{ theme: state.theme, toggleTheme: () => dispatch({ type: TOGGLE_THEME }) }}>
{children}
</ThemeContext.Provider>
);
};

// Custom hook to use theme context
export const useTheme = () => useContext(ThemeContext);

 

2. Apply Theme Styles Using CSS Variables

Define CSS variables for light and dark themes.

 

/* styles.css */
:root {
--bg-color: #ffffff;
--text-color: #000000;
}

[data-theme="dark"] {
--bg-color: #181818;
--text-color: #ffffff;
}

body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}

 

3. Implement a Theme Toggle Button

 

import { useTheme } from "./ThemeContext";

const ThemeToggleButton = () => {
const { theme, toggleTheme } = useTheme();

return (
<button onClick={toggleTheme} style={{ margin: "20px", padding: "10px" }}>
{theme === "light" ? "🌙 Switch to Dark Mode" : "☀️ Switch to Light Mode"}
</button>
);
};

export default ThemeToggleButton;

 

4. Integrate Everything in App.js

 

import { ThemeProvider } from "./ThemeContext";
import ThemeToggleButton from "./ThemeToggleButton";
import "./styles.css";

const App = () => {
return (
<ThemeProvider>
<div style={{ textAlign: "center", padding: "20px" }}>
<h2>Global Theme Provider</h2>
<ThemeToggleButton />
<p>The theme will persist after page reloads.</p>
</div>
</ThemeProvider>
);
};

export default App;

 

How It Works:

  1. Global Theme Management with Context API
    • The theme state is available throughout the app.
    • No prop drilling needed.
  2. Efficient State Updates with useReducer
    • Uses an action-based reducer to toggle the theme.
  3. Persisting Theme in localStorage
    • Ensures the theme remains the same after refresh.
  4. CSS Variables for Styling
    • Styles are automatically updated without modifying components.

Why This Approach?

  • Global theme state with easy access anywhere in the app.
  • Smooth user experience with theme persistence.
  • Performance-friendly using useReducer and CSS variables.
React Developer hiring resources
Hire React Developers
Hire fast and on budget—place a request, interview 1-3 curated developers, and get the best one onboarded by next Friday. Full-time or part-time, with optimal overlap.
Hire now
Q&A about hiring React Developers
Want to know more about hiring React Developers? Lemon.io got you covered
Read Q&A

Hire remote React developers

Developers who got their wings at:
Testimonials
star star star star star
Gotta drop in here for some Kudos. I’m 2 weeks into working with a super legit dev on a critical project, and he’s meeting every expectation so far 👏
avatar
Francis Harrington
Founder at ProCloud Consulting, US
star star star star star
I recommend Lemon to anyone looking for top-quality engineering talent. We previously worked with TopTal and many others, but Lemon gives us consistently incredible candidates.
avatar
Allie Fleder
Co-Founder & COO at SimplyWise, US
star star star star star
I've worked with some incredible devs in my career, but the experience I am having with my dev through Lemon.io is so 🔥. I feel invincible as a founder. So thankful to you and the team!
avatar
Michele Serro
Founder of Doorsteps.co.uk, UK

Simplify your hiring process with remote React developers

Popular React Development questions

How does React handle form validation? – Lemon.io

React supports form validation by providing the developer with a means to manage the state of the forms and their respective validation logic within the components. Using controlled components, it becomes pretty straightforward to validate input fields in real time since all values of the inputs of the form will be managed by React state. For more advanced form management and validation, libraries such as Formik or React Hook Form can be utilized, allowing for the handling of complex forms with less boilerplate code.

What are the best practices for managing state in React applications? – Lemon.io

React applications can further be optimized with respect to the management of state by making use of best techniques like raising the state up the component tree to the nearest common ancestor of components that need access to it. Use Redux or Context API for complex state management. Minimize the states store only what is necessary and this will minimize the number of rerenders. Also, instead of useState, when there is more complex state logic, useReducer will lead to cleaner and maintainable code.

How does React improve the performance of web applications? – Lemon.io

React optimizes the performance of web applications by making use of a virtual DOM; hence, it reduces the amount of changes it makes on the actual DOM. It minimizes time spent rendering and resources utilized in rendering updates since React will only update those components that have changed. This leads to faster and more responsive user interfaces. Besides that, it allows developers to load parts of an application that are in use at a certain moment by splitting the code and making lazy loading work.

What are the advantages of using React for web development? – Lemon.io

React has a lot of advantages in web development; above all, it’s component-based architecture. The whole thing allows the developer to easily create reusable UI components. Next, it features a Virtual DOM, enhancing its performance through its great ability to update and render components at incredible speed. React has this one-way flow of data in user interface components, making it very easy to debug and test. Last but not least, React’s growing ecosystem—which also includes, among others, tools like Redux and React Router—already sports robust solutions for state management and routing. That is why React would still manage to hold its position as one of the leading choices in designing scalable and maintainable web applications.

Is React used by Full-Stack Developers? – Lemon.io

Yes, a Full-Stack Developer uses React for the web app Front-End. They create dynamic and responsive user interfaces and then manage the Back-End via Node.js, Express, or other technologies for server-side frameworks. Among those reasons is that React can be connected with many different Back-end technologies, hence helping one effectively develop end-to-end applications.

What is React Development? – Lemon.io

React Development refers to the process of building a user interface, primarily single-page applications. A developer can use it to create reusable UI elements, update, and render for performance and maintainability efficiently. The tool is geared toward creating independent components handling their state but possibly combined to form complex and highly interactive user interfaces. Accordingly, React Development has become a well-known aspect in the development of fast, dynamic, and scalable web applications.

image

Ready-to-interview vetted React developers are waiting for your request