React State Management

React State Management

Module 1: State Management in React

1. What is State in React?

State in React refers to an object that stores data specific to a component. It represents the dynamic data that can change over time and directly impact how the component renders.

For example:

  • If you have a button that toggles between dark and light mode, the color mode is stored in the state.

  • If you have a shopping cart, the list of added items is stored in the state.

State is mutable, meaning it can be updated within the component. Whenever the state changes, React automatically re-renders the component to reflect the new data.

2. Difference Between State and Props

Feature

State

Props

Definition

Internal data storage of a component

External data passed from parent to child component

Mutability

Can be updated using state management techniques

Cannot be changed once received from the parent

Where Used?

Used within the component that defines it

Used to pass data between components

Triggers Re-render?

Yes, when state updates, the component re-renders

No, props do not trigger a re-render on their own

Example in real-world terms:

  • State is like your personal shopping cart—it keeps track of items you have added.

  • Props are like a gift package—someone else (a parent component) gives it to you, and you can use it, but you cannot modify it.

3. Why Do We Need React State Management?

In simple applications, managing state manually inside components is easy. However, as applications grow, managing and sharing state between components becomes challenging.

Key Reasons for React State Management:

  1. Avoiding UI Inconsistencies

    • Example: If an e-commerce app does not manage state properly, adding a product to the cart may not reflect immediately in the UI.

  2. Better Component Communication

    • Example: If a user logs in, multiple components (profile, dashboard, navbar) need access to that login state.

  3. Reducing Complexity

    • Example: Manually passing data through multiple levels of components can be tedious and inefficient.

  4. Handling External Data

    • Example: Data from an API (like fetching user details) needs to be stored and managed properly.

4. Types of State in React

React State Management  can be classified into four main types based on its scope and usage.

1. Local State

  • This state is managed within a single component.

  • It controls small, isolated changes, such as toggling a modal, managing input fields, or tracking a counter.

  • Example: A button that switches between “Show More” and “Show Less” in a component.

2. Global State

  • This state is shared between multiple components.

  • It is used when multiple parts of an application need access to the same data.

  • Example: A logged-in user’s details need to be accessed in the navbar, dashboard, and settings page.

3. Server State

  • This refers to data fetched from an external API or database.

  • It needs to be synchronized between the server and the client.

  • Example: A weather app fetching temperature data from an API.

4. URL State

  • This is the state stored in the browser URL.

  • It helps in navigation and search functionalities.

  • Example:

    • Query parameters in a search engine (?query=React).

    • Page numbers in a paginated list (/products?page=3).

Module 2: Managing State with useState

1. Understanding useState Hook

React State Management provides the useState Hook to manage local state in function components. Local state means the data is stored within a specific component and does not directly affect other components.

Why is useState Important?

  • It allows components to handle dynamic data and re-render when the data changes.

  • Unlike traditional variables, useState ensures the UI updates automatically when the state changes.

Example Use Cases

  • Toggling a button (like dark/light mode).

  • Managing form inputs (such as text fields in a login form).

  • Tracking UI interactions (like opening/closing a modal).

2. How to Declare and Update State in Function Components

When using useState, we follow two key steps:

  1. Declare State: Define a variable that holds state and initialize it with a default value.

  2. Update State: Modify the state using a function provided by useState.

How State Updates Work

  • When we update the state, React re-renders the component to reflect the changes.

  • The previous value is replaced with the new value.

Example Scenarios

  • A counter that starts at 0 and increases when a button is clicked.

  • A toggle switch that alternates between ON and OFF.

3. Working with Primitive Values vs. Complex Objects in State

State can hold different types of data:

1. Primitive Values

  • These include numbers, strings, booleans (simple values).

  • When updating state, the new value replaces the old value.

  • Example: A counter value (number), a user’s name (string), or a toggle switch (boolean).

2. Complex Objects and Arrays

  • Sometimes, state needs to store objects or arrays (complex data).

  • Updating such state requires handling nested properties carefully.

  • Example:

    • A user profile storing name, email, and age.

    • A shopping cart storing multiple products in an array.

Challenges with Complex State

  • When updating an object, you must ensure that only the necessary property is changed while keeping the other properties intact.

  • Arrays require adding, removing, or modifying elements carefully to prevent unexpected behavior.

4. Example: Counter Application Using useState

A counter application is a simple example to understand local state. Here’s how it works:

  1. The state variable holds the counter value (starting at 0).

  2. When a button is clicked, the state updates by increasing or decreasing the value.

  3. React re-renders the component, displaying the updated counter.

This example highlights how useState dynamically updates the UI in response to user interactions.

5. Common Mistakes and Best Practices

 Best Practices:

Initialize State Properly

  • Always provide an initial value for useState, such as 0, “”, false, or an empty object {}.

 Update State Correctly

  • Use the provided update function instead of modifying state directly.

 Handle Complex State Carefully

  • When updating objects or arrays, spread existing values to retain unchanged properties.

Optimize Performance

  • Avoid unnecessary re-renders by updating state only when needed.

 Common Mistakes:

 Directly Modifying State

  • Modifying state variables directly does not trigger re-renders in React.

 Updating State Incorrectly in Loops or Conditions

  • Always ensure state updates follow the correct structure to avoid unexpected behavior.

 Not Handling Previous State Properly

  • For complex updates (like counters), using the previous state correctly ensures consistent updates. 

Module 3: Context API Global State Management

Managing state across multiple components in a React application can become challenging, especially when the same data needs to be accessed in different parts of the app. Context API provides a simple solution for handling global state without the need for complex state management libraries like Redux.

1. Why Context API?

In a React State Management application, state is often passed from a parent component to a child component using props. However, as the application grows, passing props through multiple levels (also known as “prop drilling”) becomes inefficient and hard to manage.

Problems with Prop Drilling:

  • Every component between the source of the data (parent) and the consumer of the data (child) has to pass the prop, even if they don’t use it.

  • If the state needs to be shared across multiple components, it can lead to unnecessary re-renders.

Solution: Context API

Context API eliminates the need for prop drilling by allowing data to be shared across components without manually passing props. It acts as a global store where data can be accessed from any component within the application.

2. Creating a Global State Using createContext

To manage global state with Context API, we need to create a context using createContext().

How It Works:

  1. Define a Context: This is like a container that holds the shared data.

  2. Provide the Data: A Provider component wraps the part of the application that needs access to this data.

  3. Consume the Data: Any component inside the Provider can access the global state using useContext().

When to Use Context API?

  • User Authentication: Store user login state globally.

  • Theme Management: Switch between light and dark modes across the app.

  • Language Preferences: Support multiple languages without prop drilling.

3. Using useContext to Access Global State

Once a global React State Management is created using createContext, components can access the shared state using the useContext Hook.

How useContext Helps:

  • It allows components to directly read from the global state without needing props.

  • The state updates dynamically when the Provider’s value changes, ensuring UI updates automatically.

Example Use Cases:

  • A user profile page displaying logged-in user details.

  • A shopping cart icon showing the total number of items across different pages.

4. Provider and Consumer Components

The Provider and Consumer components play key roles in managing Context API:

 Provider Component

  • Stores and provides data to all child components.

  • Wrapping components inside the Provider ensures they can access the context.

 Consumer Component

  • Any component inside the Provider can use the Consumer to access the context data.

  • The useContext() Hook is now commonly used instead of the traditional Consumer component.

Real-World Example:

  • The theme switcher of a website where the entire UI updates when a user switches between dark and light mode.

  • Language selection in an application that instantly translates text based on the chosen language.

  1. Example: Managing User Authentication State with Context API

A common use case for Context API is user authentication management. Let’s consider a real-world example:

Scenario:

A React application has multiple components:

  • Login Page → Users log in.

  • Dashboard → Displays user information.

  • Navbar → Shows “Login” or “Logout” depending on authentication status.

Without Context API:

  • The authentication state (whether the user is logged in or not) would need to be passed through props to every relevant component.

  • This makes the application structure messy, with prop drilling becoming a problem.

With Context API:

  1. Create an Authentication Context to store user login status.

  2. Use a Provider Component to wrap the entire app and store authentication state.

  3. Access the Auth State from any component (Navbar, Dashboard) using useContext().

Benefits:

  • No need to manually pass user data between components.

  • The UI automatically updates when login status changes.

  • Clean and scalable approach for managing global authentication state.

Module 4: useReducer for Complex React State Management

When working with React applications, state management can become complex, especially when dealing with multiple related state values. The useReducer Hook provides a structured way to manage state updates in a predictable manner, making it a great choice for handling complex logic.

1. Introduction to useReducer Hook

useReducer is a React Hook used for managing state in a more structured way than useState. It is based on the

style reducer pattern, where state changes are managed by a function called a reducer.

Why Use useReducer?

  • It centralizes state logic into a single function (the reducer).

  • It is useful when state updates depend on previous state or involve multiple steps.

  • It helps maintain readability and maintainability in large applications.

Common Use Cases:

  • Managing a shopping cart in an e-commerce site.

  • Handling form inputs with complex validation rules.

  • Managing authentication state (logged in/out).

2. Difference Between useState and useReducer

Feature

useState

useReducer

Best For

Simple state (single values)

Complex state (multiple related values)

State Update Method

Uses a function to update state directly

Uses a reducer function with dispatch and actions

Structure

Stores and updates state in a component

Centralizes state logic into a reducer function

Use Case

Toggle buttons, form inputs

Shopping carts, authentication, form validation

Example Scenarios

  • useState is great for simple states like a counter or toggle switch.

  • useReducer is better when multiple state variables need to be updated together (e.g., adding/removing items from a cart).

3. How useReducer Works (Actions, Dispatch, Reducers)

The useReducer Hook works with three key concepts:

1 Actions

  • Actions are objects that describe what kind of state update is needed.

  • Example: “ADD_TO_CART”, “REMOVE_ITEM”, “INCREMENT”

2 Dispatch Function

  • The dispatch function is used to send (or “dispatch”) an action to the reducer.

  • Example: If a user clicks “Add to Cart,” we dispatch an action to update the cart state.

3 Reducer Function

  • The reducer function takes the current state and the action and returns the new state.

  • It acts like a state manager, ensuring that updates happen in a structured way.

How the Flow Works:

  1. A user performs an action (e.g., clicking a button).

  2. The dispatch function is called with the action.

  3. The reducer function processes the action and updates the state.

  4. The component re-renders with the new state.

4. Example: Managing a Shopping Cart State

A shopping cart is a classic example of using useReducer because:

  • It involves multiple state updates (adding, removing, updating items).

  • It needs to keep track of quantities, prices, and total cost.

  • The logic is easier to manage when centralized in a reducer.

Shopping Cart Use Case:

User Actions:

  • Add an item to the cart

  • Remove an item from the cart

  • Increase or decrease item quantity

How useReducer Helps:

  • The reducer function manages all cart-related actions in one place.

  • The dispatch function allows components to trigger cart updates without direct state manipulation.

  • The cart state remains predictable and easy to debug.

5. When to Use useReducer Instead of useState

Choosing between useState and useReducer depends on the complexity of state management.

Use useState When:

  •  The state is simple (like a single counter or form input)

  • State updates are independent (not related to previous values).

  •  The logic is straightforward (like toggling a modal).

Use useReducer When:

The state has multiple values that change together (like a cart).
 State updates depend on the previous state (like toggling multiple selections).
 The application has complicated state logic that would be hard to manage with useState.
 You need a centralized state management approach for clarity.

Module 5: State Management with Redux

As React applications grow in complexity, managing state efficiently across multiple components becomes a challenge. Redux provides a centralized approach to state management, ensuring that data flows in a predictable manner across the application.

1. What is Redux and Why Use It?

Redux is a state management library that helps manage the application’s global state in a predictable way. It follows a unidirectional data flow, making it easier to debug and maintain complex applications.

Why Use Redux Instead of Context API or useReducer?

  • Centralized State Management: Redux stores all application state in a single place (Store), making it easier to manage and access.

  • Predictable State Updates: State changes follow a strict pattern, ensuring consistency across the application.

  • Easier Debugging: Redux provides tools like Redux DevTools to track every state change, making debugging simpler.

  • Scalability: Suitable for large applications where multiple components need to access and modify shared data.

When to Use Redux?

  • Applications with large, shared, and frequently updated state (e.g., authentication, cart management).

  •  Multi-page applications that need persistent state across different views.

  • Collaborative apps (e.g., real-time dashboards, chat apps).

2. Core Concepts of Redux

Redux operates using three main building blocks:

1 Store

  • The Store is a centralized container that holds the global state of the application.

  • All components can access the state without prop drilling.

2 Actions

  • Actions are plain JavaScript objects that describe what should happen in the application.

  • They are the only way to send data to the Redux Store.

  • Example:

    • “LOGIN_USER” → Logs in a user.

    • “ADD_TO_CART” → Adds an item to the shopping cart.

3 Reducers

  • A Reducer is a function that takes the current state and an action and returns the new state.

  • It does not modify the state directly but returns a new copy of the state.

  • Example:

    • If an action “INCREMENT” is dispatched, the reducer updates the count by 1.

    • If “ADD_TO_CART” is dispatched, the reducer adds an item to the cart state.

3. Setting Up Redux in a React Project

To use Redux in a React project, we follow these steps:

  1. Install Redux and react-redux

     

    • Redux manages the state, while react-redux connects Redux with React components.

  2. Create the Redux Store

     

    • The Store holds the application state.

  3. Define Actions

     

    • Actions describe what changes should happen.

  4. Create Reducers

    • Reducers specify how state should change based on actions.

  5. Provide the Store to the React App

    • Use the <Provider> component from react-redux to give all components access to the Redux state.

4. Using react-redux (Provider, useSelector, useDispatch)

1 Provider Component

  • The Provider component makes the Redux Store available to all React components.

  • It is placed at the root of the app.

2 useSelector Hook

  • useSelector allows components to access the state from the Store.

  • Example: A navbar component accessing the authentication state to show “Login” or “Logout”.

3 useDispatch Hook

  • useDispatch allows components to trigger actions that update the Store.

  • Example: A login button dispatching an “LOGIN_USER” action to update authentication state.

How These Work Together:

  1. A user performs an action (e.g., clicking “Add to Cart”).

  2. useDispatch sends an action to the Redux Store.

  3. The Reducer updates the state in the Store.

  4. Components using useSelector get the updated state and re-render.

5. Example: Managing User Authentication with Redux

Scenario:

A React application has:

  • A login page where users enter their credentials.

  • A dashboard that displays user information if logged in.

  • A navbar that shows “Login” or “Logout” based on authentication status.

How Redux Helps:

  1. The authentication state (isLoggedIn) is stored in the Redux Store.

  2. When a user logs in, a “LOGIN_USER” action is dispatched.

  3. The Reducer updates the authentication state in the Store.

  4. The Navbar and Dashboard automatically update using useSelector to reflect the login status.

Benefits of Using Redux for Authentication:

 The authentication state is managed in one place and shared across multiple components.


 No need for prop drilling between different parts of the app.


 The login/logout state persists across different pages and UI components.

Module 6: Advanced Redux with Redux Toolkit

As applications grow, managing Redux state manually with actions and reducers can become boilerplate-heavy. Redux Toolkit (RTK) simplifies Redux development by providing a more efficient and structured way to manage state, reducing the need for extra code.

1. What is Redux Toolkit (RTK)?

Redux Toolkit (RTK) is the official recommended way to write Redux applications. It provides a simpler and more efficient API for managing state while reducing boilerplate code.

Why Use Redux Toolkit?

  •  Less Boilerplate: No need to manually create action types and action creators.

  • Built-in Best Practices: Encourages the correct use of Redux (immutability, structured state updates).

  •  Better Performance: Uses modern approaches for optimizing state updates.

  •  Integrated Middleware: Comes with tools for async actions (like API calls) and debugging.

Key Features of Redux Toolkit:

  • createSlice – Combines actions and reducers into a single function.

  • configureStore – Simplifies store setup and automatically adds middleware.

  • RTK Query – A built-in tool for handling API requests efficiently.

2. Setting Up Redux Toolkit

To use Redux Toolkit, we follow these steps:

1 Install Redux Toolkit (@reduxjs/toolkit) and React Redux (react-redux).
2 Create a Slice (using createSlice) to define state and reducers.
3 Configure the Redux Store (using configureStore).
4 Use React Redux Hooks (useSelector and useDispatch) to interact with the state.

3. Creating Slices with createSlice

A slice is a self-contained piece of Redux state with:

  • An initial state

  • A set of reducers (functions that modify state)

  • Auto-generated actions for modifying the state

Instead of separately defining actions, reducers, and action creators, createSlice groups them into a single unit.

Example Use Case:

A user authentication slice can contain:

  • An initial state (isAuthenticated: false)

  • A reducer to handle login/logout actions

  • Actions like loginSuccess, logout

Why Use createSlice?

Automatically generates action types and action creators.


 Makes code cleaner and easier to maintain.


 Encourages modular state management (dividing state into slices).

4. Using configureStore to Set Up the Store

Traditionally, setting up a Redux store required:

  • Manually combining reducers

  • Adding middleware

  • Configuring the Redux DevTools

With configureStore, Redux Toolkit automates this process and sets up:
 A preconfigured Redux Store


Built-in support for Redux DevTools


 Default middleware for handling async logic

 

Why Use configureStore?

  • No need to manually configure middleware

  • Automatically merges slices into a single store

  • Easier setup for large applications

5. Example: Managing API Calls with Redux Toolkit & RTK Query

One of Redux Toolkit’s biggest advantages is RTK Query, a built-in solution for handling API requests efficiently. It simplifies fetching, caching, and updating API data without extra libraries like Axios or Redux Thunk.

How RTK Query Works

 1 Define an API slice using createApi.
2 Define endpoints (e.g., fetching user data).
3 Use generated hooks (useGetUserQuery) to fetch data in React components.

Why Use RTK Query?

 No need for manual API calls inside actions.
 Built-in caching and automatic refetching.
 Reduces code needed for managing API state.



Module 7: React State Management with Recoil

As React applications grow, managing state efficiently becomes critical. Recoil is a modern state management library designed specifically for React, offering a simpler and more flexible approach compared to Redux or Context API.

1. Introduction to Recoil

Recoil is a lightweight and flexible state management library that integrates seamlessly with React’s component-based architecture.

Why Use Recoil?

  •  Minimal Setup – No need for complex reducers, actions, or boilerplate code.

  •  Efficient Performance – Uses a unique dependency tracking system, preventing unnecessary re-renders.

  •  Scalable – Suitable for both small and large applications without additional complexity.

  •  Better Reusability – Allows easy sharing of state across components.

How Recoil Differs from Redux & Context API

 

Feature

Recoil

Redux

Context API

Boilerplate Code

  Minimal

  High

  Low

Global State

  Yes

  Yes

  Yes

Performance

  High (only updates affected components)

  Can cause unnecessary re-renders

  May cause prop drilling issues

Learning Curve

  Easy

  Steep

  Easy

Async State Management

  Built-in

  Requires middleware

  Requires manual handling

2. Atoms and Selectors

Recoil state is managed using Atoms and Selectors.

1 Atoms – The Building Blocks of Recoil State

  • Atoms are units of state that can be shared across components.

  • Any component that uses an atom automatically re-renders when the atom changes.

  • Atoms act as a global state that can be accessed from anywhere in the app.

2 Selectors – Derived State Management

  • Selectors are functions that compute derived state from atoms.

  • They allow for efficient state transformation without modifying the original state.

  • Used for filtering, calculations, or combining multiple atoms.

3. Managing State Using Recoil Hooks

Recoil provides hooks to interact with atoms and selectors:

1 useRecoilState

  • Works like React’s useState but for Recoil atoms.

  • Allows reading and updating atom state.

2 useRecoilValue

  • Used to read the value of an atom or selector without updating it.

3 useSetRecoilState

  • Used to update the state of an atom without reading its value.

4 useRecoilValueLoadable & useRecoilStateLoadable

  • Used for handling asynchronous state (e.g., API calls).

 

4. Comparison with Context API and Redux

Feature

Recoil

Redux

Context API

Ease of Use

  Simple

Requires setup

Simple

Performance

  Optimized re-renders

Can cause re-renders

Can cause re-renders

Async State

  Built-in

  Requires middleware

  Manual handling

Global State

  Yes

  Yes

  Yes

Setup Complexity

  Minimal

  High

  Low

When to Choose Recoil?
 If you need a lightweight state management solution with minimal setup.
 If performance optimization (avoiding unnecessary re-renders) is a priority.
 If your app has highly interactive components that depend on shared state.

 

5. Example: Theme Toggler Using Recoil

A common use case for Recoil is a theme toggler (switching between light and dark mode).

How Recoil Helps:

 1 A theme atom stores the current theme state.
2 A toggle function updates the atom’s value (light ⇄ dark).
3 Any component using the theme atom automatically updates when the theme changes.

Why Use Recoil for This?

 No need for prop drilling—any component can access and modify the theme state.
 Efficient updates—only affected components re-render.
 Simple to implement compared to Redux.

Module 8: Zustand Lightweight State Management

State management in React can sometimes be complex, especially with solutions like Redux and Context API. Zustand is a lightweight, minimalistic state management library that offers a simpler and more efficient way to manage application state without unnecessary boilerplate.

1. What is Zustand?

Zustand (German for “state”) is a fast, scalable, and minimal state management library for React. It simplifies global state management while maintaining flexibility and performance.

Why Use Zustand?

  •  Zero Boilerplate – No need for reducers, actions, or dispatch functions.

  •  Lightweight & Fast – Less than 1KB in size and highly optimized.

  •  No Provider Required – Unlike Context API, Zustand doesn’t need a <Provider> wrapper.

  •  Selective Renders – Updates only components that use the state, preventing unnecessary re-renders.

  •  Supports Middleware – Works well with persisted state, logging, and dev tools.

2. Creating and Using Zustand Stores

How Zustand Works

Zustand stores state in a centralized store and provides functions to update the state. Instead of using a global Redux store or Context API, Zustand allows components to directly subscribe to the state they need.

Key Features of a Zustand Store

 1 State Definition – Create a store with an initial state.
2 State Modification – Define functions to update the state.
3 State Access – Components can read and modify the state.

 

How It Differs from Redux & Context API

Feature

Zustand

Redux

Context API

Boilerplate Code

  Minimal

  High

  Low

Global State

  Yes

  Yes

  Yes

Performance

  Optimized

  Can cause re-renders

  Can cause re-renders

Setup Complexity

  Easy

  Complex

  Easy

Requires Provider?

  No

  Yes

  Yes

Middleware Support

  Yes

  Yes

  No

3. Advantages of Zustand Over Redux & Context API

 Less Complexity – No need to manually define actions, reducers, or dispatchers.
 Better Performance – Only updates components that use the state (unlike Context API, which may trigger unnecessary renders).
 No Extra Dependencies – Zustand doesn’t require additional tools or configurations like Redux Toolkit.
 Simple API – Uses plain JavaScript objects and functions instead of complex reducers.

When to Choose Zustand?

 If you need a simple, scalable, and efficient way to manage global state.
 If you want to avoid unnecessary re-renders and improve app performance.
 If you don’t want to deal with Redux’s complexity and setup.

4. Example: Managing Authentication State with Zustand

A common use case for Zustand is user authentication.

How Zustand Helps in Authentication:

 1 The store contains user authentication state (isAuthenticated).
2 Functions like login and logout modify the authentication state.
3 Components subscribe to the state and automatically update when the user logs in or out.

Why Use Zustand for This?

 No need for Context API’s <Provider>.
 Components automatically subscribe only to the necessary state.
 Cleaner code compared to Redux, reducing boilerplate.

Module 9: Server State Management with React Query

Managing server state efficiently in React State Management applications can be challenging. Unlike local state, server state is fetched from an external source (API, database) and needs to be cached, synchronized, and updated dynamically. React Query simplifies this process by handling API requests, caching, background fetching, and automatic updates.

1. Introduction to React Query

React Query is a powerful library for managing server state in React applications. It helps handle data fetching, caching, synchronization, and state updates efficiently.

Why Use React Query?

  •   Automatic Caching – Stores API responses to avoid unnecessary re-fetching

  • Background Fetching – Fetches fresh data without blocking the UI.

  •  Automatic Synchronization – Keeps server data updated in real time.

  •  Optimistic Updates – Improves user experience by updating the UI before confirming changes.

  •  Error Handling – Provides built-in loading, error, and retry mechanisms.

Why React Query Instead of useEffect?

Feature

React Query

useEffect + useState

API Caching

  Yes

  No

Background Fetching

  Yes

  No

Automatic Refetching

  Yes

  No

Error Handling

  Yes

  Manual

Optimistic Updates

  Yes

  No

2. Handling API Calls Efficiently

React Query replaces manual API calls (fetch or axios) inside useEffect and manages everything, including:

 1 Fetching Data – Automatically fetches data when needed.
2 Caching Responses – Stores API responses in memory to reduce network requests.
3 Background Refetching – Keeps data fresh without affecting user experience.
4 Pagination & Infinite Scrolling – Handles large datasets efficiently.
5 Error & Loading States – Built-in retry logic for failed API calls.

Key React Query Features:

  • Queries (useQuery) – Fetch and cache server data.

  • Mutations (useMutation) – Modify data (POST, PUT, DELETE requests).

  • Query Invalidation – Automatically refetches data when something changes.

3. Caching, Background Fetching & Synchronization

1 Caching – Reducing Unnecessary API Calls

React Query caches API responses automatically, preventing repeated network requests.

  • If a user navigates away and comes back, React Query reuses cached data instead of refetching.

  • Cache expiration settings allow manual or automatic data refresh.

2 Background Fetching – Keeping Data Fresh

Instead of showing outdated data, React Query silently refetches data in the background.

  • Example: If a user opens a dashboard, the data is instantly available from cache while a background request fetches the latest data.

3 Automatic Synchronization – Always Up-to-Date Data

React Query ensures that:

  • Data updates in real-time without manually re-fetching.

  • When the user switches tabs or refocuses the browser, fresh data is fetched automatically.

4. Example: Fetching and Updating Data Using React Query

A common use case for React Query is fetching and updating user data from an API.

How React Query Helps in API Calls:

 1 A query fetches user data and caches it.
2 If the user updates their profile, a mutation sends the update request.
3 The query cache is automatically invalidated, refetching fresh data.

Why Use React Query for This?

No need for manual API calls in useEffect.
 Faster performance due to caching and background fetching.
 Automatic UI updates when the server data changes.

Module 10: Best Practices & Choosing the Right State Management Tool

State management in React is crucial for building scalable and maintainable applications. However, with multiple state management options available, it’s essential to understand when and why to use a particular approach.

This module compares different React state management tools, discusses performance optimization techniques, and helps in choosing the best solution for various use cases.

  1. Comparing Different State Management Approaches

 useState vs. useReducer

Feature

useState

useReducer

Simplicity

Very simple

  Requires more setup

Best for

Small, simple state updates

Complex state logic with multiple actions

Re-renders

More frequent

Optimized (only runs reducer function)

Performance

Good for UI-based state

Better for state with multiple transitions

Example Use Case

Input fields, toggle buttons

Shopping cart, form state management

  • Use useState when dealing with local UI state, such as form inputs, modals, or toggles.

  • Use useReducer when managing complex state transitions, like updating a shopping cart or handling multiple actions in one state object.

 Context API vs. Redux

Feature

Context API

Redux

Boilerplate Code

  Minimal

  High

Best for

Small to medium apps

Large-scale apps

Performance

  Can cause re-renders

  Optimized (via selectors)

Middleware Support

  No

  Yes (Redux Thunk, Saga)

Learning Curve

  Easy

  Steep

  • Use Context API for lightweight global state, like theme settings or authentication.
     

  •  Use Redux for large applications where state management needs to be centralized, predictable, and optimized for performance.

 Recoil vs. Zustand

Feature

Recoil

Zustand

Boilerplate

  Low

  Very Low

Best for

Large apps needing global state

Simple, lightweight state management

Performance

  Optimized

  Faster than Context API

Requires Provider?

  Yes

  No

Async State Management

  Built-in

  Requires manual setup

  • Use Recoil if you want a simple yet scalable state management solution with built-in async support.

  • Use Zustand for minimal, highly efficient state management without needing a provider.

React Query for API Data

Unlike the other tools, React Query is not for managing local state. Instead, it handles server state efficiently, ensuring real-time synchronization, caching, and automatic updates.

  •  Use React Query when working with API data to avoid unnecessary re-fetching and improve performance.

2. Performance Optimization Techniques

Regardless of which state management tool is used, optimizing performance is essential to avoid unnecessary re-renders and ensure a smooth user experience.

1 Avoid Unnecessary Re-renders

 Use memoization (React.memo, useMemo, useCallback) to prevent unnecessary recalculations.
 Optimize context providers by splitting them into smaller contexts to avoid excessive updates.
 Use selectors in Redux/Zustand to only re-render components when necessary.

2 Optimize API Calls

 Use React Query for automatic caching and background updates.
Implement debouncing to reduce frequent API calls (e.g., for search inputs).
 Use lazy loading for fetching data only when required.

3 Use Efficient Data Structures

 Avoid deep object nesting in state, as it can slow down updates.
 Use immutable updates to prevent unnecessary re-renders.

3. When to Use Which State Management Solution? 

Use Case

Best Solution

Small UI-based state (e.g., toggle buttons, input fields)

useState

Complex local state with multiple actions (e.g., form handling, shopping cart)

useReducer

Light global state (e.g., theme settings, authentication)

Context API

Large-scale apps needing structured state management 

Redux

Minimal and fast global state management

Zustand

Efficient API data handling and caching

React Query

Modern, scalable state management alternative

Recoil

 

Conclusion

State management plays a crucial role in React applications, especially as they scale. Inefficient handling of state can lead to performance bottlenecks, such as unnecessary re-renders, sluggish UI updates, and excessive memory usage. To build fast, scalable, and maintainable applications, developers need to adopt best practices and optimization techniques.

In this guide, we explored various strategies to enhance state management efficiency, ensuring smooth performance across all React applications.

Key Takeaways for Optimizing State Management

1 Avoiding Unnecessary Renders

  •  Use React.memo to prevent components from re-rendering when props remain unchanged.

  •  Ensure state updates only when necessary, avoiding redundant state modifications.

  •  Avoid passing new object/array references as props unless required.

  • Reduce overuse of the Context API, as it can trigger unnecessary renders in deeply nested components.

2 Using Memoization (useMemo, useCallback)

  • Use useMemo to cache expensive computations, preventing unnecessary recalculations on every render.

  •  Use useCallback to memoize functions, avoiding function re-creations that trigger child component re-renders.

  •  Optimize components by ensuring functions inside them do not change unnecessarily.

3 Implementing Selectors for Efficient State Access

  •  In Redux, use selectors to extract only the required part of the state instead of accessing the entire store.

  • Avoid passing large state objects to components—use derived state or useSelector() in Redux with reselect.

  • For the Context API, use multiple small contexts instead of a single large context to prevent excessive renders.

4 Lazy Loading State and Components

  • Defer state initialization in useState by passing a function to avoid unnecessary re-executions.

  • Use React.lazy() and Suspense to load heavy components only when needed, reducing the initial load time.

  •  For larger applications, split code into dynamic imports for improved performance.

5 Debugging and Monitoring State Changes

  • Use React Developer Tools to analyze unnecessary re-renders.

  • Use useEffect to log state changes and identify unexpected updates.

  • Leverage React Profiler to measure component performance and optimize rendering behavior.

FAQs

1. What is state in React?

State in React is an object that holds dynamic data and determines a component’s behavior and rendering. It is managed within components and updated using functions like setState or hooks like useState.

  • Local state is confined to a specific component and managed using useState or useReducer.
  • Global state is shared across multiple components and managed using tools like the Context API, Redux, Recoil, or Zustand.
  • Keep data consistent across components
  •  Reduce prop drilling and unnecessary re-renders
  • Improve performance and maintainability
  • Use useState for simple state logic (e.g., toggles, form inputs).
  • Use useReducer when dealing with complex state transitions (e.g., form management, state dependent on previous values).

Prop drilling occurs when a prop is passed through multiple components just to reach a deeply nested child.

Solutions:

  •  Use Context API to share state globally
  •  Use state management libraries like Redux or Recoil

The Context API provides a way to pass data through the component tree without prop drilling. It is useful for theme settings, authentication, and global state sharing.

  •  Context API causes unnecessary re-renders if not optimized
  •  Difficult to debug in large applications
  •  Not ideal for frequent state updates (use Redux or Recoil instead)

Redux is a predictable state container for managing global state in React apps. It is useful for complex applications where multiple components need shared state updates.

  • Redux Toolkit (RTK) is a modern approach that simplifies Redux setup by providing utilities like createSlice and configureStore.
  • Classic Redux requires more boilerplate code for actions, reducers, and store configuration.

Recoil is a lightweight state management library that allows components to subscribe to atoms (state units). Unlike Redux, Recoil does not require a central store and allows direct subscriptions to individual state pieces.

  •  Small to medium apps
  •  Avoiding Redux boilerplate
  •  Managing global state with minimal effort

Jotai is an atomic state management library similar to Recoil. It allows components to subscribe to small state units (atoms) and updates only the necessary parts of the UI.

  • React Query for caching and background data fetching
  • SWR for fetching API data with automatic revalidation
  • Redux Toolkit Query (RTK Query) for managing server state efficiently
  •  Use React.memo to prevent re-renders when props don’t change
  •  Use useCallback to memoize functions
  •  Use useMemo to optimize expensive calculations
  • Use Selectors in Redux/Context API to extract only necessary state
  • useState & useReducer – Best for local component state
  • Context API – Good for small-scale global state (e.g., authentication, themes
  •  Redux / Redux Toolkit – Best for large applications with complex state logic
  • Recoil / Zustand / Jotai – Lightweight alternatives to Redux for global state
  • React Query / SWR / RTK Query – Best for managing async API state

Enroll For Free Demo