Skip to content

muhammadjareer24/Top-React-Interview-Questions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

Top React Interview Questions

This repository provides a collection of React Interview Questions to help developers prepare for React.js job interviews. The questions range from basic to advanced concepts and cover various aspects of React development, including hooks, lifecycle methods, Redux, and performance optimization.

Table of Contents

  1. JSX
  2. Virtual DOM
  3. Reconciliation in React
  4. Props vs State
  5. useState vs useEffect
  6. Higher-Order Components (HOC)
  7. useCallback vs useMemo
  8. Redux
  9. Redux Thunk
  10. Class Components vs Functional Components
  11. Hooks in React
  12. useEffect vs Lifecycle Methods
  13. Limitations of the Virtual DOM
  14. useCallback and Performance
  15. useMemo vs useCallback
  16. Redux vs useState
  17. Side Effects in React
  18. Alternatives to Redux
  19. Controlled vs Uncontrolled Components
  20. Context API vs Redux
  21. What is Prop Drilling?
  22. Pure Component vs Regular Component
  23. useReducer vs useState
  24. React Fragments
  25. Reconciliation Without Keys
  26. Lazy Loading and Code Splitting
  27. What is an Error Boundary?
  28. Explain Redux
  29. Performance Optimization in React

jsx

What is JSX, and how is it different from HTML?

JSX (JavaScript XML) is a syntax extension for JavaScript used in React. It allows developers to write HTML-like code inside JavaScript and is primarily used to structure the UI in React applications. However, JSX is not valid HTML or JavaScript, so it must be transpiled into JavaScript using tools like Babel before the browser can process it.

Key Differences:

  • JavaScript Logic: JSX allows embedding JavaScript expressions inside curly braces {}. This means you can dynamically insert values or logic directly into the markup.
  • Strict Syntax: Unlike HTML, JSX enforces that all elements must be properly closed, including self-closing tags like <img /> and <br />.
  • Attribute Naming: In JSX, attributes like class and for are written as className and htmlFor to avoid conflicts with JavaScript keywords.

This is much more powerful than plain HTML because it allows you to combine UI with dynamic data directly.

Why Use JSX?
JSX simplifies how we write UI components by allowing us to blend JavaScript and markup. This makes the code easier to read and maintain, especially when building complex, dynamic applications where UI needs to change based on data.

Example: Let’s say we want to greet a user by name. With JSX, you can easily embed JavaScript logic inside the UI like this:

const user = "Jane";
return <h1>Welcome, {user}!</h1>; // Embeds JavaScript logic into JSX

Back to top


Virtual DOM

What is the Virtual DOM, and how does React use it?

The Virtual DOM is a lightweight, in-memory representation of the actual DOM. React uses the Virtual DOM to improve performance and efficiency when rendering UI updates. Instead of directly manipulating the real DOM, React updates the Virtual DOM first. Once the changes are made, React compares (or "diffs") the new Virtual DOM with the previous version, and only the parts that have changed are updated in the real DOM. This process is called reconciliation.

Key Benefits:

  • Performance: By updating only the parts of the DOM that have changed, React avoids costly full-page re-renders, making updates faster.
  • Efficiency: React reduces unnecessary DOM manipulations, improving overall app performance by minimizing direct interactions with the real DOM.

Example:
Let’s say you have a list of items and only one item changes. Instead of re-rendering the entire list, React will update only the specific item that changed in the real DOM after making the changes in the Virtual DOM first.

const items = ['Apple', 'Banana', 'Cherry'];

// Only the changed item gets updated in the real DOM.
function updateItem(index, newItem) {
  const updatedItems = [...items];
  updatedItems[index] = newItem;
  return updatedItems;
}

Reconciliation in React

What is Reconciliation in React?

Reconciliation is the process React uses to update the real DOM efficiently. When the state or props of a component change, React creates a new Virtual DOM and compares it with the previous version. This comparison (or "diffing") allows React to determine what has changed. Instead of updating the entire DOM, React only updates the parts that have changed, making the process much faster and more efficient.

Key Benefits:

  • Minimal Updates: React calculates the smallest number of changes needed and applies them, reducing the performance cost of full DOM updates.
  • Optimized Rendering: By updating only the affected parts of the UI, React ensures that the application runs smoothly even with frequent updates.

Example:
Imagine you have a shopping cart, and you update the quantity of one item. Instead of re-rendering the entire cart, React will only update the specific item whose quantity changed.

const cart = [
  { name: 'Apple', quantity: 1 },
  { name: 'Banana', quantity: 2 }
];

// Only the updated item in the cart will be rendered again
function updateQuantity(index, newQuantity) {
  const updatedCart = [...cart];
  updatedCart[index].quantity = newQuantity;
  return updatedCart;
}

Back to top


Props vs State

  1. What are props and state in React? (props vs state)

Props and state are two key concepts in React that help manage data in components, but they serve different purposes.

  • Props: Short for properties, props are read-only inputs passed from a parent component to a child component. They are used to pass data and configuration down the component tree. Since props are immutable, the receiving component cannot modify them.
  • State: State is a local data structure managed within a component. It is mutable and can be changed based on user interactions or events within the component. Changes to the state will trigger a re-render of the component to reflect the updated state.
Props State
Passed from parent to child component. Managed within the component itself.
Immutable (cannot be modified). Mutable (can be updated).
Used for passing data and configuration. Used for managing component’s internal data.
Controlled by parent component. Controlled by the component itself.
Does not trigger re-renders directly. Triggers re-renders when updated.

Example:
Consider a counter component where props might pass an initial value, and state manages the current count:

function Counter({ initialCount }) {  // Props: initialCount
  const [count, setCount] = useState(initialCount);  // State: count
  
  return (
    <div>
      <p>Current count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Back to top


useState vs useEffect

  1. Explain the difference between useState and useEffect in React.

In React, useState and useEffect are two of the most commonly used hooks that help manage component behavior, but they serve very different purposes.

useState

  • Purpose: useState is used to manage local state within a functional component. It allows you to declare a state variable and provides a function to update it. When the state changes, React re-renders the component to reflect the new state.
  • When to Use: Use useState whenever you need to keep track of information that will change over time, like form inputs, counters, or toggle switches.

useEffect

  • Purpose: useEffect is used for side effects. A "side effect" refers to anything that affects something outside of the component's rendering process (e.g., fetching data from an API, subscribing to services, setting up timers, or directly manipulating the DOM). By default, useEffect runs after every render, but you can control when it runs using a dependency array.
  • When to Use: Use useEffect whenever you need to perform an action after the component renders or re-renders (e.g., data fetching, DOM updates, or subscribing to events).
useState useEffect
Used to manage local state in a component. Used to manage side effects in a component.
Triggers a re-render when the state changes. Runs after every render (or re-render), based on dependencies.
Stores data that belongs to the component. Handles external interactions like data fetching or subscriptions.
Example: managing form inputs, toggles. Example: fetching data when the component mounts.

Example: useState vs useEffect
Let’s build a simple counter component that updates the document title with the count value using both useState and useEffect.

import { useState, useEffect } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // useState to track count

  // useEffect to update the document title whenever the count changes
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);  // Dependency array ensures effect runs when `count` changes

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

In this example:

  • useState is used to track the count.
  • useEffect is used to update the document title whenever the count changes.

Back to top


Higher-Order Components (HOC)

  1. What is a Higher-Order Component (HOC) in React?

    Higher-Order Components (HOC)

A Higher-Order Component (HOC) is a function in React that takes a component and returns a new component with additional props or behavior. HOCs are used to reuse logic across multiple components without modifying the components themselves. Instead, they "wrap" the original component and add functionality.

HOCs follow the concept of higher-order functions in JavaScript, where functions take other functions as arguments or return them.

Why Use HOCs?

HOCs allow you to share logic across components without duplicating code. Common use cases include:

  • Adding Authentication: Wrapping components to check if a user is authenticated before rendering.
  • Logging: Wrapping components to log certain events or props.
  • Data Fetching: Fetching data and passing it down as props to the wrapped component.

Example of HOC:

Let’s create a simple HOC that adds authentication logic to any component. If the user is not authenticated, it redirects them to the login page.

import React from 'react';
import { Redirect } from 'react-router-dom';

// Higher-Order Component that adds authentication
const withAuth = (WrappedComponent) => {
  return (props) => {
    const isAuthenticated = props.isAuthenticated; // Assume this is passed as a prop
    
    if (!isAuthenticated) {
      return <Redirect to="/login" />;
    }
    
    return <WrappedComponent {...props} />;
  };
};

// Usage example: Wrap any component with `withAuth`
const Dashboard = (props) => {
  return <h1>Welcome to the Dashboard!</h1>;
};

export default withAuth(Dashboard); // Dashboard now requires authentication

Back to top


useCallback vs useMemo

  1. What is the difference between useCallback and useMemo?

useCallback vs useMemo

In React, useCallback and useMemo are hooks that optimize the performance of your components by memoizing values and functions. They both help to prevent unnecessary re-renders or recalculations, but they are used in different scenarios.

useCallback

  • Purpose: useCallback is used to memoize functions, preventing their re-creation on every render. This is useful when you want to pass stable function references to child components to avoid unnecessary re-renders.
  • When to Use: Use useCallback when passing callbacks as props to child components, especially if those components are wrapped in React.memo() (which prevents re-renders if props don’t change).

useMemo

  • Purpose: useMemo is used to memoize the return value of a function. It helps avoid expensive calculations or object recreations unless the dependencies change.
  • When to Use: Use useMemo when you have a computationally expensive function or calculation that shouldn’t re-run unless its dependencies change.
useCallback useMemo
Memoizes functions. Memoizes the result of a function (values).
Returns a memoized version of the callback function that only changes if dependencies change. Returns the memoized value of a computation that only re-runs if dependencies change.
Useful for optimizing when passing callbacks to child components. Useful for optimizing expensive calculations.

Example of useCallback:

Let’s say you have a parent component that passes a function as a prop to a child component. Without useCallback, the function would be recreated on every render, causing unnecessary re-renders of the child.

import { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';

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

  // useCallback to prevent function recreation on every render
  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return <ChildComponent increment={increment} />;
}

Back to top


Redux

  1. What is Redux, and why is it used?

What is Redux, and why is it used?

Redux is a state management library often used with React to manage and centralize application state. It provides a predictable way to manage state across your entire app, making it easier to debug, track, and manage complex state changes, especially in larger applications.

Why Use Redux?

  1. Centralized State Management: Redux creates a single source of truth (the store) for the entire app's state. This makes it easier to manage state across different components.
  2. Predictability: The state in Redux is predictable because it can only be changed by dispatching actions, which are processed by pure functions called reducers. This makes it easier to trace changes and debug state.
  3. Time Travel Debugging: Redux enables time travel debugging, which means you can step through actions and see how the state changes over time, making it extremely helpful for debugging.
  4. Easier State Sharing: Redux allows multiple components to access the same state without passing props down multiple layers, which is common in larger applications.

Key Concepts in Redux:

  • Store: The store holds the entire state of your application. There is typically only one store in a Redux application.
  • Action: Actions are plain JavaScript objects that describe an event or change in the app (e.g., a button click). Every action must have a type property that indicates the type of action being performed.
  • Reducer: Reducers are pure functions that specify how the state changes in response to actions. They take the current state and an action, and return the new state.
  • Dispatch: The dispatch function sends actions to the Redux store, triggering the reducer to update the state based on the action.

Example:

Let’s build a simple Redux example where we manage a counter state.

  1. Action: Defines the type of action we want to perform, such as incrementing the counter.
  2. Reducer: Specifies how the state changes when the action is dispatched.
  3. Store: Holds the state and provides methods to interact with it.
// Action
const increment = () => {
  return { type: 'INCREMENT' };
};

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

// Store
import { createStore } from 'redux';
const store = createStore(counterReducer);

// Dispatch an action to update the state
store.dispatch(increment());
console.log(store.getState());  // Output: 1

Back to top


Redux Thunk

  1. What is Redux Thunk?

What is Redux Thunk?

Redux Thunk is a middleware that allows you to write asynchronous logic in Redux. By default, Redux actions are synchronous, meaning they can only dispatch plain objects. However, Redux Thunk allows you to write action creators that return functions instead of objects, enabling you to handle asynchronous operations like API calls, timers, or other side effects within Redux.

Why Use Redux Thunk?

  1. Handling Asynchronous Operations: Redux Thunk lets you dispatch actions after an asynchronous task (e.g., fetching data) is completed, making it easier to manage async workflows in your app.
  2. Delaying Action Dispatch: With Thunk, you can delay the dispatch of an action until certain conditions are met (e.g., waiting for API data).
  3. More Control Over Dispatching: Redux Thunk provides more granular control over when and how actions are dispatched, allowing you to create more dynamic and interactive applications.

How Redux Thunk Works:

Without Thunk, actions are dispatched as plain JavaScript objects. With Redux Thunk, an action creator can return a function that accepts dispatch as an argument. Inside that function, you can execute asynchronous code and dispatch actions when necessary.

Example of Redux Thunk:

Let’s create a simple example where we fetch user data from an API and use Redux Thunk to handle the asynchronous request.

  1. Action: Define an action creator that returns a function instead of a plain object.
  2. Reducer: Handle the dispatched actions to update the state.
  3. Store: Use applyMiddleware() to include Thunk in the Redux store.
// Action creator with Redux Thunk for asynchronous logic
const fetchUser = () => {
  return async (dispatch) => {
    dispatch({ type: 'FETCH_USER_REQUEST' });
    
    try {
      const response = await fetch('https://api.example.com/user');
      const data = await response.json();
      
      dispatch({ type: 'FETCH_USER_SUCCESS', payload: data });
    } catch (error) {
      dispatch({ type: 'FETCH_USER_FAILURE', error: error.message });
    }
  };
};

// Reducer to handle different states of the user data
const userReducer = (state = { loading: false, user: null, error: null }, action) => {
  switch (action.type) {
    case 'FETCH_USER_REQUEST':
      return { ...state, loading: true };
    case 'FETCH_USER_SUCCESS':
      return { ...state, loading: false, user: action.payload };
    case 'FETCH_USER_FAILURE':
      return { ...state, loading: false, error: action.error };
    default:
      return state;
  }
};

// Store setup with Redux Thunk middleware
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const store = createStore(userReducer, applyMiddleware(thunk));

// Dispatching the async action
store.dispatch(fetchUser());

Back to top


Class Components vs Functional Components

  1. What is the difference between class components and functional components in React?

What is the difference between class components and functional components in React?

In React, components can be written as either class components or functional components. Both are used to build the UI, but they differ in syntax, features, and how they handle state and lifecycle methods.

Class Components:

  • Syntax: Class components are written using the class keyword, and they extend React.Component.
  • State Management: Class components have their own state and manage it using this.state and this.setState().
  • Lifecycle Methods: Class components have access to lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount.
  • this keyword: Class components use this to refer to the component instance, which can sometimes lead to issues with binding methods.

Functional Components:

  • Syntax: Functional components are plain JavaScript functions that return JSX. They are simpler and more concise.
  • Hooks for State and Side Effects: Functional components use hooks like useState and useEffect to manage state and side effects, respectively.
  • No this keyword: Functional components don't use the this keyword, which simplifies the code and avoids issues with method binding.
  • Stateless vs. Stateful: Originally, functional components were stateless, but since the introduction of hooks in React 16.8, functional components can now manage state and perform side effects just like class components.
Class Components Functional Components
Use the class syntax to define a component. Use plain JavaScript functions to define a component.
Manage state with this.state and this.setState(). Manage state with the useState hook.
Have access to lifecycle methods. Use hooks like useEffect to handle side effects.
Require the use of the this keyword. No this keyword is needed.
More verbose and require more boilerplate. Simpler and more concise, especially with hooks.

Example of Class Component:

import React, { Component } from 'react';

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

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

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

Back to top


Hooks in React

  1. Can you explain how hooks work in React?

Example of React Functional Component with Hooks

This component demonstrates the use of two of the most important hooks in React: useState and useEffect. It is designed to show how hooks simplify state management and side effects, while making your code more concise and readable. This example can be an attractive talking point during an interview as it shows proficiency with modern React practices.

Key Features:

  • Dynamic State Management: The component uses useState to track the number of button clicks.
  • Side Effects with useEffect: It also leverages useEffect to update the document's title whenever the count changes, demonstrating how to handle side effects in functional components.
  • Clean and Concise: The code avoids complexity, using a simple and elegant structure without the need for class components or lifecycle methods.

Full Code:

import React, { useState, useEffect } from 'react';

function ClickCounter() {
  // useState hook to manage the count state
  const [count, setCount] = useState(0);

  // useEffect hook to update document title based on count
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]); // The effect runs whenever 'count' changes

  return (
    <div style={{ textAlign: 'center', marginTop: '50px' }}>
      <h1>React Click Counter</h1>
      <p style={{ fontSize: '20px' }}>You clicked the button {count} times</p>
      <button
        style={{
          padding: '10px 20px',
          fontSize: '16px',
          backgroundColor: '#007BFF',
          color: '#FFF',
          border: 'none',
          borderRadius: '5px',
          cursor: 'pointer',
        }}
        onClick={() => setCount(count + 1)}
      >
        Click Me
      </button>
    </div>
  );
}

export default ClickCounter;

Back to top


useEffect vs Lifecycle Methods

  1. Why do we need useEffect, and how does it differ from lifecycle methods?

Why do we need useEffect, and how does it differ from lifecycle methods?

In React, useEffect is a hook that lets you perform side effects in functional components. Side effects include things like data fetching, manually changing the DOM, setting up subscriptions, or timers. Before hooks were introduced in React, these tasks could only be handled in class components using lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount. The useEffect hook simplifies this by combining all the lifecycle methods into a single, unified API.

Why do we need useEffect?

  1. Side Effect Management: React components focus on rendering the UI. However, most applications also need to handle side effects, such as fetching data from an API, subscribing to services, or updating the document title. useEffect provides a simple, declarative way to handle these side effects in functional components.
  2. Replacement for Lifecycle Methods: In class components, you needed multiple lifecycle methods to handle different stages of the component's lifecycle (componentDidMount, componentDidUpdate, componentWillUnmount). With useEffect, all these can be handled in one place.
  3. Declarative: useEffect runs after the component renders and can be configured to run only when certain dependencies change, giving you fine-grained control over when side effects should occur.

Differences from Lifecycle Methods:

useEffect (Functional Components) Lifecycle Methods (Class Components)
Combines multiple lifecycle phases into one hook. Requires separate methods like componentDidMount, componentDidUpdate, and componentWillUnmount.
Can be used with multiple effects by defining separate useEffect calls. Requires tracking lifecycle phases manually.
Runs after the component renders. Lifecycle methods run at different points in a component's lifecycle.
Easier to understand and manage side effects with fewer lines of code. Requires more boilerplate and can be more complex to manage.

Example:

Let’s create a functional component that fetches data using useEffect, simulating how you would handle side effects like componentDidMount in a class component:

import React, { useState, useEffect } from 'react';

function UserProfile() {
  const [userData, setUserData] = useState(null);

  // useEffect to fetch user data when the component mounts
  useEffect(() => {
    async function fetchUser() {
      const response = await fetch('https://api.example.com/user/1');
      const data = await response.json();
      setUserData(data);
    }

    fetchUser();
  }, []); // Empty array ensures this effect runs only once, similar to componentDidMount

  if (!userData) {
    return <p>Loading...</p>;
  }

  return (
    <div>
      <h1>{userData.name}</h1>
      <p>{userData.email}</p>
    </div>
  );
}

Back to top


Limitations of the Virtual DOM

  1. What are the limitations of React’s Virtual DOM?

What are the limitations of React’s Virtual DOM?

The Virtual DOM is one of the key concepts that makes React performant and efficient. It allows React to update only the parts of the DOM that have changed, rather than re-rendering the entire page. However, despite its advantages, the Virtual DOM has some limitations that can impact performance in certain scenarios.

Limitations of React's Virtual DOM:

  1. Large and Complex Applications:

    • As the size and complexity of your application grow, the time taken to diff (compare) the Virtual DOM with the real DOM increases. This can result in performance bottlenecks, especially in applications with a very deep or large component tree.
    • Example: A dashboard with many nested components might slow down because React needs to diff each level of the component hierarchy, increasing the time for reconciliation.
  2. Frequent Re-Renders:

    • While React’s Virtual DOM optimizes updates, frequent or unnecessary re-renders still have a performance cost. Even though React only updates the changed parts of the DOM, excessive re-renders caused by improper state management or overuse of certain hooks (e.g., useEffect) can degrade performance.
    • Example: If multiple components are re-rendering due to state changes at the parent level or due to passing new references to functions and objects, React will repeatedly diff the Virtual DOM, which can cause slowdowns.
  3. Memory Overhead:

    • The Virtual DOM requires React to maintain an in-memory representation of the DOM. This adds additional memory usage, especially in larger applications where the Virtual DOM can grow large and consume a significant amount of resources.
    • Example: On memory-constrained devices like older smartphones, the additional overhead of maintaining a large Virtual DOM may lead to performance issues.
  4. Not a Silver Bullet:

    • The Virtual DOM is optimized for most UI updates but doesn't necessarily solve all performance issues. Sometimes, manual DOM manipulation with direct optimizations (e.g., using shouldComponentUpdate or React.memo) is necessary for highly interactive or real-time applications.
    • Example: In a real-time application (like a live chat or stock market ticker), where updates occur every second, using React’s Virtual DOM might still introduce lag because of the constant diffing process.
  5. Overhead in Simple Applications:

    • For very simple or static applications, the Virtual DOM can add unnecessary complexity and overhead. In these cases, using vanilla JavaScript or frameworks without a Virtual DOM (like Svelte) might offer better performance with less overhead.
    • Example: A static website with minimal dynamic interactions might not benefit from the Virtual DOM, and using it could result in more code and performance overhead than is necessary.

Summary of Limitations:

Limitation Description
Large component trees Diffing a large number of components can slow down reconciliation.
Frequent re-renders Excessive re-renders can degrade performance, even with optimized Virtual DOM.
Memory overhead The Virtual DOM consumes additional memory, which can be problematic for large apps.
Not ideal for real-time applications Constant updates can overwhelm the Virtual DOM's diffing process in real-time apps.
Overhead in simple apps The Virtual DOM can be overkill for static or very simple applications.

How to Mitigate These Limitations:

  • Use React.memo and PureComponent: These can help prevent unnecessary re-renders by shallowly comparing props and state, avoiding costly Virtual DOM diffs.
  • Use shouldComponentUpdate: In class components, you can control when a component should re-render, reducing unnecessary diffs.
  • Code Splitting: Break your large components into smaller pieces and use lazy loading to improve the performance of larger applications.
  • Proper State Management: Make sure that state changes only trigger updates when necessary, and avoid re-rendering components that haven’t changed.

In summary, while React’s Virtual DOM is a powerful optimization technique, it isn’t perfect. In highly complex or real-time applications, performance can still degrade if the Virtual DOM is overused or mismanaged. Proper optimization techniques, such as memoization and efficient state management, are essential to mitigating these limitations.

useCallback and Performance

  1. How does useCallback help with performance in React?

How does useCallback help with performance in React?

useCallback is a React hook that helps improve performance by memoizing functions. It prevents the unnecessary re-creation of functions during re-renders, which is especially important when passing callbacks to child components. By using useCallback, you ensure that the same function instance is passed unless its dependencies change, thus avoiding performance issues caused by unnecessary re-renders.

Why is this important for performance?

In React, every time a component re-renders, a new instance of any function inside the component is created. This can lead to performance issues if you pass these functions as props to child components, especially when those components are optimized with React.memo. When the reference of a function changes, even though the function's logic remains the same, the child component may unnecessarily re-render.

useCallback helps by memoizing the function, so that the function reference only changes when one of its dependencies changes.

Example:

Here’s an example where useCallback is used to optimize a parent-child component interaction:

import React, { useState, useCallback } from 'react';

// Child component that only re-renders when its props change
const Button = React.memo(({ handleClick }) => {
  console.log('Button rendered');
  return <button onClick={handleClick}>Click Me</button>;
});

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

  // useCallback to memoize the increment function
  const increment = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  return (
    <div>
      <h1>Count: {count}</h1>
      <Button handleClick={increment} />
    </div>
  );
}

export default ParentComponent;

Back to top


useMemo vs useCallback

  1. When should you use useMemo vs useCallback?

When should you use useMemo vs useCallback?

Both useMemo and useCallback are React hooks that help optimize performance by memoizing values and functions. They serve different purposes and should be used in specific scenarios.

useMemo

  • Purpose: useMemo is used to memoize the result of a computation. It ensures that an expensive calculation is only re-computed when one of its dependencies changes. It is useful when you have costly operations (like filtering, sorting, or processing large datasets) that shouldn't run on every render.
  • When to Use: Use useMemo when you want to memoize a computed value to avoid recalculating it on every render. The value will only be recomputed if the dependencies change.

useCallback

  • Purpose: useCallback is used to memoize functions. It prevents a function from being recreated on every render, which is useful when passing callbacks as props to child components, especially those optimized with React.memo. This ensures the child component only re-renders if the function actually changes.
  • When to Use: Use useCallback when you want to memoize a function to avoid unnecessary re-creation of the same function instance on every render.
useMemo useCallback
Memoizes the result of a function (value). Memoizes the function itself.
Useful for optimizing expensive calculations. Useful for optimizing functions passed to child components (e.g., event handlers).
Returns a cached value. Returns a cached function.
Example: Memoizing filtered or sorted lists. Example: Memoizing a function passed to React.memo child.

Example of useMemo:

Imagine you have a large list of items and you only want to display items based on a specific filter. Without useMemo, the filtering logic would run on every render, even if the input data hasn't changed.

import { useMemo } from 'react';

function ItemList({ items, filterTerm }) {
  // useMemo to memoize the filtered items array
  const filteredItems = useMemo(() => {
    console.log('Filtering items...');
    return items.filter(item => item.includes(filterTerm));
  }, [items, filterTerm]); // Recompute only if items or filterTerm changes

  return (
    <ul>
      {filteredItems.map(item => <li key={item}>{item}</li>)}
    </ul>
  );
}

Back to top


Redux vs useState

  1. How does Redux differ from React’s built-in state management using useState?

How does Redux differ from React’s built-in state management using useState?

Both Redux and React's built-in state management (useState) are used to manage state in a React application, but they serve different purposes and are suited for different use cases.

useState (React's Built-in State Management):

  • Local State: useState is used to manage local component-level state. Each component can have its own independent state, which is isolated from other components.
  • Simple and Lightweight: It’s the simplest way to manage state in React components, with no additional setup required. Ideal for managing state in individual components or small apps.
  • Tightly Coupled: The state managed by useState is tightly coupled to the component. Passing state between components requires prop drilling (passing props from parent to child).

Redux (Global State Management):

  • Global State: Redux manages global state that is shared across many components in an application. It provides a single source of truth for the entire app’s state, which is accessible from any component.
  • Predictable State Changes: Redux enforces a strict pattern where state changes happen through actions and reducers, making it easier to track and predict state changes, especially in large applications.
  • Complex Setup: Redux requires additional setup with actions, reducers, and a store, making it more complex than useState. However, this complexity is often justified in large-scale apps with complex state management needs.
  • Scalability: Redux is more suitable for applications where state needs to be shared across many components, and where you want to avoid prop drilling or complex state management logic.
useState Redux
Manages local component state. Manages global state across the entire app.
Easy to set up and use, great for simple state. More complex setup, but great for large apps.
State is tightly coupled to individual components. State is decoupled and stored globally in a centralized store.
Changes directly via setState. State changes happen through actions and reducers.
Prop drilling required to share state across components. No prop drilling; components can access global state using connect or useSelector.
Ideal for small to medium-sized applications. Ideal for large, complex applications.

Example: Managing a Counter with useState

In smaller applications, useState is perfect for managing simple, localized state, like a counter:

import React, { useState } from 'react';

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

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

export default Counter;

Back to top


Side Effects in React

  1. What are side effects in React, and how do you handle them?

What are side effects in React, and how do you handle them?

In React, side effects refer to any action that affects something outside the scope of the component, such as interacting with external APIs, updating the DOM directly, setting up subscriptions, or managing timers. Unlike rendering, which is a pure function (it always returns the same output for the same input), side effects can introduce unpredictability because they interact with external systems or the browser environment.

Examples of Side Effects:

  • Fetching data from an API.
  • Setting up a subscription (e.g., WebSocket or real-time data feed).
  • Manually manipulating the DOM (e.g., changing the document title).
  • Setting up timers or intervals.

How to Handle Side Effects in React:

React provides the useEffect hook to handle side effects in functional components. The useEffect hook allows you to run side-effect code after the component renders, ensuring that side effects do not interfere with the rendering process.

Basic Syntax of useEffect:

useEffect(() => {
  // Your side effect code here (e.g., fetching data or updating the DOM)

  return () => {
    // Cleanup function (optional), runs when the component unmounts or before the effect re-runs
  };
}, [dependencies]);  // Dependency array controls when the effect re-runs

Back to top


Alternatives to Redux

  1. What are the alternatives to Redux for state management in React?

While Redux is a powerful tool for managing state in React applications, it’s often considered too complex for small to medium-sized apps. Several alternatives can handle state management with less boilerplate and complexity.

1. React’s Context API

The Context API is a built-in feature in React for sharing state globally without needing to pass props down multiple layers (prop drilling). It allows you to easily manage global state and share it across components without external libraries.

When to Use:

  • Ideal for small to medium apps where global state sharing is necessary.
  • A lightweight replacement for prop drilling.

Key Benefits:

  • Built-in to React.
  • Easy to set up and lightweight.
  • Works well for simple global state management.

2. MobX

MobX is a simple and scalable state management library that uses a reactive approach. It allows state to be mutable and automatically updates the UI when the state changes. MobX is more flexible and has less boilerplate than Redux.

When to Use:

  • Suitable for apps requiring real-time or highly dynamic state updates.
  • Ideal when you want less boilerplate and direct state manipulation.

Key Benefits:

  • Reactive updates for real-time apps.
  • Simpler than Redux, with automatic reactivity.
  • Less boilerplate and easy to learn.

3. Recoil

Recoil is a state management library designed specifically for React. It provides fine-grained control over state re-renders and allows for easy management of global state with minimal setup.

When to Use:

  • Ideal for large apps where state management needs to be efficient and scalable.
  • Great for apps requiring shared state across multiple components without prop drilling.

Key Benefits:

  • Atom-based state management that is easy to scale.
  • Optimized re-rendering, making it efficient for complex apps.
  • Tight integration with React.

4. Zustand

Zustand is a lightweight, small, and fast state management library that provides a minimal API for creating global state. It allows direct state mutations without the need for actions or reducers, making it easy to use in smaller projects.

When to Use:

  • Best for small to medium apps needing simple global state management.
  • Ideal for projects requiring minimal setup and high performance.

Key Benefits:

  • Minimal API with direct state updates.
  • Less complexity and overhead compared to Redux.
  • Fast and efficient for smaller applications.

5. React Query (for async state)

React Query is a library focused on handling asynchronous state, such as data fetching and caching. While it doesn’t manage client-side state like Redux, it excels at managing server-side state, making it easier to fetch, cache, and synchronize remote data.

When to Use:

  • Perfect for applications that rely heavily on server-side data and need effective management of async state.
  • Ideal when the focus is on handling remote data and caching.

Key Benefits:

  • Simplifies data fetching, caching, and synchronization.
  • Manages async state without complex setup.

Comparison of Alternatives:

Library When to Use Key Benefits
React Context API Small apps with minimal global state needs or to avoid prop drilling. Built-in to React, lightweight.
MobX Apps requiring reactive and real-time state updates. Automatic reactivity, less boilerplate.
Recoil Larger apps requiring efficient global state management and shared state. Fine-grained control, atom-based, easy to scale.
Zustand Small to medium apps needing simple state management with minimal setup. Minimal API, direct state updates, high performance.
React Query Apps focused on fetching and managing server-side data (async state). Handles data fetching and caching efficiently.

Conclusion:

While Redux is powerful and widely used, it can be overkill for smaller or simpler projects. Alternatives like React’s Context API, MobX, Recoil, Zustand, and React Query offer simpler and more lightweight solutions for state management, depending on the size and complexity of your application. Choosing the right tool depends on your specific project needs.

Back to top


Controlled vs Uncontrolled Components

In React, controlled components and uncontrolled components are two different ways of handling form inputs.

  • Controlled Components: In a controlled component, form data is handled by the React component’s state. The form elements’ value is controlled by the state, and any updates to the form are done through event handlers that update the state. Controlled components give you full control over the form data.

  • Uncontrolled Components: In an uncontrolled component, form data is handled by the DOM itself, rather than the React state. You can access form values using ref to interact directly with the DOM elements. Uncontrolled components are simpler, but provide less control over the form data.

Key Differences:

Controlled Components Uncontrolled Components
The state manages the form data in React. The DOM handles the form data directly.
Uses onChange handlers to update state. Uses ref to access form values from the DOM.
More control over form data validation and logic. Simpler and requires less boilerplate code.
React updates form elements with state changes. The form element maintains its own state.

Back to top


Context API vs Redux

20. Context API vs Redux

Both the Context API and Redux are tools used for managing global state in React applications. However, they serve different purposes and are used in different scenarios depending on the complexity and scalability needs of the application.

  • Context API: The Context API is built into React and allows you to share state across components without passing props down manually at every level (also known as prop drilling). It's a simple solution for state management in small to medium-sized applications and works best for less complex state structures.

  • Redux: Redux is an external library used for managing the global state of large-scale applications. It follows a predictable state container pattern where state is managed through actions and reducers, ensuring state changes are trackable and more manageable as the application grows.

Key Differences:

Context API Redux
Built into React, no external libraries needed. Requires an external library (redux).
Suitable for small to medium-sized applications. Suitable for large, complex applications.
Easy to implement with minimal boilerplate. More setup and boilerplate required (actions, reducers, store).
State changes are less predictable and not strictly managed. State changes are predictable and traceable via actions.
Great for avoiding prop drilling. Best for managing large global state with many state dependencies.

When to Use Context API:

  • When you need to manage a small amount of global state.
  • To avoid prop drilling in applications with deeply nested components.
  • When minimal setup and simplicity are prioritized.

When to Use Redux:

  • When you have a large application with complex state logic.
  • When you need more predictable and traceable state management with actions and reducers.
  • When debugging and time-travel debugging are needed for state changes.

Back to top


What is Prop Drilling?

21. What is Prop Drilling?

Prop Drilling occurs when data is passed from a parent component to deeply nested child components via multiple layers of intermediary components. This approach becomes problematic when components that don’t need the data are forced to pass it along, leading to less maintainable code and unnecessary complexity.

Example of Prop Drilling:

function Grandparent() {
  const [user, setUser] = useState('John Doe');
  return <Parent user={user} />;
}

function Parent({ user }) {
  return <Child user={user} />;
}

function Child({ user }) {
  return <p>User: {user}</p>;
}

Back to top


Pure Component vs Regular Component

22. Pure Component vs Regular Component

In React, Pure Components and Regular Components (or "class components") differ in how they handle updates and rendering optimization.

  • Pure Component: A PureComponent in React is a class component that automatically implements shouldComponentUpdate with a shallow comparison of props and state. This means that a pure component will only re-render if there is a change in its props or state, making it more efficient in terms of performance for certain use cases.

  • Regular Component: A regular class component does not implement shouldComponentUpdate by default. This means that it will re-render whenever its parent re-renders, even if the props or state haven't changed. You need to manually implement shouldComponentUpdate to control when the component should re-render.

Key Differences:

Pure Component Regular Component
Implements shouldComponentUpdate by default with a shallow prop and state comparison. Does not implement shouldComponentUpdate by default.
Only re-renders when there is a change in props or state (based on shallow comparison). Re-renders whenever the parent component re-renders, unless shouldComponentUpdate is manually implemented.
Suitable for components with simple, immutable data structures. Suitable for components where state or props are frequently changing.
Can optimize performance by preventing unnecessary re-renders. May cause performance issues due to unnecessary re-renders.

Example:

Pure Component:

import React, { PureComponent } from 'react';

class MyPureComponent extends PureComponent {
  render() {
    console.log('Pure component re-rendered');
    return <div>{this.props.name}</div>;
  }
}

Regular Component:

import React, { Component } from 'react';

class RegularComponent extends Component {
  render() {
    console.log('Regular Component Rendered');
    return <div>Regular Component - {this.props.name}</div>;
  }
}

export default RegularComponent;

Back to top

===

useReducer vs useState

23. useReducer vs useState

In React, both useReducer and useState are hooks used to manage state, but they serve different purposes and are suited for different use cases.

  • useState: The useState hook is simpler and ideal for managing local state in functional components. It's most commonly used when you have a small number of state variables or when the state transitions are straightforward.

  • useReducer: The useReducer hook is more powerful and is typically used when you have more complex state logic or when the state is an object with multiple sub-values. It works similarly to Redux by using a reducer function to manage state updates, which makes it ideal for handling state with complex transitions.

Key Differences:

useState useReducer
Simpler to use for managing local state. Better for managing complex state transitions.
State updates are made directly using setState. State updates are made using a reducer function that takes the current state and an action.
Ideal for simple use cases (e.g., form inputs, toggles). Ideal when state depends on multiple actions or involves complex logic.
Returns an array: [state, setState]. Returns an array: [state, dispatch].
Easier for managing independent state variables. More structured, especially when managing related state variables.

Example of useState:

import React, { useState } from 'react';

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

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

Example of useReducer:

Here’s an example of using useReducer to manage a counter with multiple actions:

import React, { useReducer } from 'react';

// Define the initial state
const initialState = { count: 0 };

// Define the reducer function to handle actions
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return { count: 0 };
    default:
      throw new Error();
  }
}
function Counter() {
  // Use useReducer with the reducer function and initial state
  const [state, dispatch] = useReducer(reducer, initialState);

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

export default Counter;

Back to top


React Fragments

24. React Fragments

React Fragments let you group multiple elements without adding extra nodes to the DOM. In situations where you need to return multiple elements from a component, you might be tempted to wrap them in a <div>. However, this can lead to unwanted additional markup in the DOM. Instead, React provides Fragments as a cleaner solution to wrap multiple elements without introducing extra DOM elements.

Why Use React Fragments?

  • Avoid Extra DOM Nodes: Wrapping multiple elements in a <div> can clutter the DOM with unnecessary elements. Fragments solve this by grouping children without adding an extra wrapper element to the DOM.
  • Cleaner JSX: Fragments make the JSX structure cleaner and avoid unnecessary elements that can affect styling and layout.

Example of Using Fragments:

  1. Using <React.Fragment>:
import React from 'react';

function FragmentExample() {
  return (
    <React.Fragment>
      <h1>Title</h1>
      <p>This is a paragraph.</p>
    </React.Fragment>
  );
}

export default FragmentExample;

Back to top


Reconciliation Without Keys

25. Reconciliation Without Keys

Reconciliation is the process React uses to compare the current Virtual DOM with the new Virtual DOM to determine the minimal set of changes needed to update the actual DOM. Keys play an essential role in this process when rendering lists of elements. React uses keys to uniquely identify each element in a list, which helps optimize the reconciliation process by allowing React to track elements more efficiently.

What Happens Without Keys?

When keys are not provided, or when non-unique keys are used (such as using indices), React may not efficiently recognize changes in the list. This can lead to:

  1. Incorrect Element Matching: React may not properly match elements during the reconciliation process, resulting in incorrect updates or rendering issues.
  2. Performance Degradation: Without keys, React compares elements in a brute-force manner, potentially leading to unnecessary re-renders or inefficient updates.
  3. Unpredictable Behavior: The absence of keys can cause issues like improper reordering of elements, incorrect animations, or loss of input field focus, as React can mistakenly reuse DOM elements that should be updated or replaced.

Example Without Keys:

In this example, React may have trouble identifying which list item has changed:

function ItemList({ items }) {
  return (
    <ul>
      {items.map((item, index) => (
        <li>{item}</li> // No keys provided
      ))}
    </ul>
  );
}

Back to top


Lazy Loading and Code Splitting

26. Lazy Loading and Code Splitting

Lazy Loading is a technique where components or resources are loaded only when they are needed, rather than all at once. In a React application, this is typically done for components that are not immediately necessary, such as routes or large components that might not be visible when the app initially loads. By deferring the loading of these components, you can improve the initial load time of the application, which results in a better user experience, especially for users on slower networks.

Benefits of Lazy Loading:

  • Improved Initial Load Time: By loading only what is necessary for the first screen, you reduce the time it takes for the page to become interactive.
  • Better User Experience: Resources and components are loaded only when needed, which leads to faster interactions as users navigate through the app.
  • Reduced Bandwidth Usage: Lazy loading minimizes the amount of data that needs to be downloaded, which is especially beneficial for users with slow or limited internet connections.
Example of Lazy Loading in React:
import React, { Suspense } from 'react';

// Lazy load the component
const LazyComponent = React.lazy(() => import('./LazyComponent'));

function App() {
  return (
    <div>
      <h1>My App</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

export default App;

Code Splitting is a technique that allows you to split your JavaScript code into multiple smaller files (or chunks) so that the browser only loads the code it needs at a given time. This is especially useful in larger applications where loading the entire app's code at once can slow down the initial page load. With code splitting, you can load parts of your application on demand, which leads to better performance and faster user experiences.


Why Use Code Splitting?

  • Improved Performance: By breaking your code into smaller pieces, the browser only downloads the necessary code for the current page or view. This reduces the size of the JavaScript bundle that needs to be downloaded and executed at once.
  • Reduced Initial Load Time: Since only the code needed for the initial view is loaded, the time it takes for the application to become interactive is reduced.
  • On-Demand Loading: Additional code is only loaded when it is actually needed, such as when the user navigates to a new page or interacts with a feature.

How to Implement Code Splitting in React:

React uses dynamic imports and React.lazy() to implement code splitting. This allows you to load components only when they are needed, which helps reduce the size of your initial bundle.

Example of Code Splitting:

Here is how you can use dynamic imports and lazy loading in React to split your code into smaller bundles:

import React, { Suspense } from 'react';

// Dynamically import the component
const AboutPage = React.lazy(() => import('./AboutPage'));

function App() {
  return (
    <div>
      <h1>My App</h1>
      {/* Lazy load the AboutPage component */}
      <Suspense fallback={<div>Loading About Page...</div>}>
        <AboutPage />
      </Suspense>
    </div>
  );
}

export default App;

Back to top


What is an Error Boundary?

27. What is an Error Boundary?

Error Boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of crashing the entire application. Error boundaries help ensure that the rest of the application continues to function even when a part of it encounters an error.

Key Features of Error Boundaries:

  • Error boundaries catch errors during rendering, in lifecycle methods, and in constructor functions of the entire tree below them.
  • They are designed to handle runtime errors in React components, but do not catch errors in event handlers, asynchronous code (e.g., setTimeout, fetch), or server-side rendering.

Common Use Cases for Error Boundaries:

  • Displaying Fallback UI: When a component tree crashes due to an error, an error boundary can display a fallback UI (like a message or a simplified interface) instead of a blank page or broken UI.
  • Logging Errors: Error boundaries can be used to log errors to external services (e.g., Sentry, LogRocket) to track and debug issues in production.

When to Use Error Boundaries:

  • Critical Sections: Use error boundaries to wrap critical sections of your application, such as the main content area, to ensure that even if a bug is encountered, the rest of the app remains functional.
  • Third-Party Libraries: If you are using third-party components or libraries that you do not control, wrapping them with error boundaries can prevent your app from breaking if those components fail.

Limitations:

  • Error boundaries do not catch errors for:
    • Event handlers
    • Asynchronous code (e.g., setTimeout, Promise)
    • Server-side rendering (SSR)
    • Errors thrown in the error boundary itself

Benefits of Using Error Boundaries:

  • Improved User Experience: Instead of the entire application crashing, the error boundary gracefully handles errors and displays a fallback UI.
  • Error Logging: You can use error boundaries to log errors to an external service, helping developers track and fix issues.
  • Component Isolation: Errors are isolated to specific parts of the app, preventing the failure of one component from affecting the rest of the application.

How to Implement an Error Boundary:

Error boundaries are implemented using React class components by overriding the lifecycle methods componentDidCatch and getDerivedStateFromError. Functional components cannot be error boundaries (as of now).

Example of an Error Boundary:
import React, { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  // Update state so the next render will show the fallback UI
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  // Log the error
  componentDidCatch(error, errorInfo) {
    console.error('Error caught by Error Boundary: ', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // Render any fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

Back to top


Explain Redux

28. Explain Redux

Redux is a predictable state container for JavaScript applications, commonly used with React. It helps manage the state of an application in a centralized store and allows any component to access this state, making state management more predictable and easier to debug.

Key Concepts of Redux:

  1. Store: The single source of truth for your application’s state. The entire state of the application is stored in an object called the store.
  2. Actions: Plain JavaScript objects that describe a change or event in the application. Each action must have a type property, which is typically a string that indicates the action being performed.
  3. Reducers: Pure functions that take the current state and an action as arguments, and return a new state. Reducers specify how the application’s state changes in response to actions.
  4. Dispatch: A method used to send actions to the Redux store. This is how you trigger state changes in Redux.
  5. Selectors: Functions used to extract specific parts of the state from the store for use in components.

Why Use Redux?

Redux is especially useful in larger applications where managing the state of many components can become complex. Some benefits of using Redux include:

  • Centralized State Management: Redux maintains the entire state of the application in a single store, making it easier to manage and debug.
  • Predictable State Updates: Since state changes are handled by reducers (pure functions), state updates are predictable and easy to trace.
  • Debugging Tools: Redux offers powerful tools like Redux DevTools for tracking state changes over time, making it easier to debug issues.

Core Principles of Redux:

  1. Single Source of Truth: The state of the entire application is stored in a single object tree within a single store.
  2. State is Read-Only: The only way to change the state is to emit an action, which describes what happened.
  3. Changes are Made with Pure Functions: To specify how the state tree is transformed by actions, you write pure reducers.

Basic Redux Flow:

  1. Action is Dispatched: A component dispatches an action when an event occurs (e.g., a user clicks a button).
  2. Reducer Updates the State: The action is sent to the reducer, which updates the state based on the action type.
  3. Store Saves the New State: The updated state is stored in the Redux store.
  4. Components Re-render: The application’s components subscribe to the store and are updated when the state changes.

Example of Redux Implementation:

  1. Action: Define actions to represent user interactions or events.
// actions.js
export const increment = () => ({
  type: 'INCREMENT',
});

export const decrement = () => ({
  type: 'DECREMENT',
});
  1. Reducer: Create a reducer that defines how the state changes in response to actions.
// reducer.js

const initialState = { count: 0 };

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

export default counterReducer;
  1. Store: Create a Redux store and provide it to your React app.
// store.js
import { createStore } from 'redux';
import counterReducer from './reducer';

const store = createStore(counterReducer);

export default store;
  1. Dispatching Actions in Components:
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './actions';

function Counter() {
 const count = useSelector((state) => state.count);
 const dispatch = useDispatch();

 return (
   <div>
     <p>Count: {count}</p>
     <button onClick={() => dispatch(increment())}>Increment</button>
     <button onClick={() => dispatch(decrement())}>Decrement</button>
   </div>
 );
}

export default Counter;

In this example:

Actions: increment and decrement actions are dispatched to change the state. Reducer: The counterReducer updates the state based on the action type. Store: The state is stored centrally and accessed via useSelector. Dispatch: The useDispatch hook is used to send actions to the store to update the state.

Tools for Redux: Redux DevTools: A powerful browser extension that lets you inspect every state change in your application, time travel, and debug issues efficiently. Redux Thunk: Middleware that allows you to write action creators that return a function instead of an action, making it possible to handle asynchronous logic (like API calls) in Redux. When to Use Redux: Large Applications: Redux is ideal for large applications with complex state interactions across many components. Global State Management: When you need a single source of truth for state that many components need to access. Predictability: When you need predictable, testable, and traceable state changes.

Back to top


29. Performance Optimization in React

Back to top

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published