DEV Community

Cover image for Redux in React: Managing Global State Like a Pro
Kathirvel S
Kathirvel S

Posted on

Redux in React: Managing Global State Like a Pro

Welcome back to Episode 12 of the “Let’s Master React Hooks Together” series

So far in this series, we’ve explored React hooks, component architecture, side effects, refs, memoization, and performance optimization. As React applications grow larger and more feature-rich, one major challenge starts appearing everywhere — managing state across multiple components efficiently.

What begins as simple prop passing between parent and child components can eventually turn into deeply nested component trees, duplicated state, inconsistent updates, and difficult-to-maintain code.

This problem becomes especially noticeable in real-world applications like:

  • E-commerce platforms
  • Social media applications
  • Admin dashboards
  • Analytics platforms
  • Project management tools

In these applications, multiple components often need access to the same shared data at the same time.

That’s exactly where Redux becomes valuable.

Redux helps React applications manage state in a centralized, predictable, and scalable way. Instead of scattering shared state across many components, Redux introduces a single source of truth that makes data flow easier to understand, debug, and maintain.

In this article, we’ll deeply understand Redux, how it works with React, why Redux Toolkit is now the standard approach, and how to structure Redux applications properly for modern frontend development.

By the end of this article, you’ll clearly understand:

  • What Redux is
  • Why Redux exists
  • Core Redux concepts
  • Redux Toolkit
  • Store setup
  • Actions and reducers
  • Async operations
  • RTK Query
  • Best practices
  • Common mistakes

What is Redux?

Redux is a predictable state management library used for JavaScript applications, especially React applications.

It provides a centralized place called a store where the entire application state is managed in a structured and organized way.

Instead of multiple components independently managing and duplicating shared data, Redux creates a single source of truth for the application.

This centralized architecture makes state changes easier to track, debug, test, and maintain as applications become larger and more complex.

Redux follows a strict one-way data flow, which makes application behavior more predictable and easier to understand during development.


Why Redux is Important

In modern frontend applications, multiple components often depend on the same piece of data.

For example:

  • Navbar → cart count
  • Product page → add to cart
  • Checkout page → cart items
  • Wishlist page → saved products
  • Profile page → user details
  • Dashboard → analytics data

Managing this shared data separately inside individual components quickly becomes difficult and error-prone.

Without centralized state management:

  • State duplication increases
  • Data synchronization becomes inconsistent
  • Prop drilling becomes excessive
  • Debugging becomes harder
  • Component communication becomes messy
  • Scaling the application becomes difficult

Redux solves these issues by introducing a centralized and predictable state container that every component can access consistently.


The Problem Redux Solves

Consider this component structure:

<App>
  <Dashboard>
    <Sidebar>
      <Profile />
    </Sidebar>
  </Dashboard>
</App>
Enter fullscreen mode Exit fullscreen mode

If Profile needs user data stored inside App, the data must pass through every intermediate component.

This process is called prop drilling.

As applications grow, prop drilling creates several problems:

  • Components receive props they don’t actually use
  • Code becomes harder to maintain
  • Component trees become cluttered
  • State management becomes difficult

Redux removes this problem completely by allowing components to directly access centralized state from anywhere inside the application.


Redux Mental Model

You can think of Redux like a banking system:

  • Store → Bank database
  • Action → Request form
  • Reducer → Bank employee processing request
  • Dispatch → Submitting the request
  • Selector → Reading account information

This analogy helps visualize how data flows inside Redux applications.


Core Redux Concepts

To understand how Redux works internally, we first need to understand the five core building blocks that power the Redux architecture.

Redux mainly revolves around five concepts:

  1. Store
  2. Actions
  3. Reducers
  4. Dispatch
  5. Selectors

Let’s understand them one by one.


1. Store

The store is the central storage for the application state.

It acts as the brain of the application and contains the complete global state tree for the entire application.

Example:

{
  user: {
    name: "John"
  },

  cart: [],

  theme: "dark"
}
Enter fullscreen mode Exit fullscreen mode

In this example:

  • user stores authentication or profile-related data
  • cart stores shopping cart items
  • theme stores UI preferences

The entire application state lives inside one centralized object, making shared data easier to manage consistently across the application.


2. Actions

Actions describe what happened inside the application.

They are plain JavaScript objects that tell Redux what type of state update should occur.

Example:

{
  type: "cart/addItem",

  payload: {
    id: 1,
    name: "Laptop"
  }
}
Enter fullscreen mode Exit fullscreen mode

Here:

  • type identifies the action
  • "cart/addItem" describes adding an item to cart
  • payload carries extra data
  • id represents product ID
  • name represents product name

Actions help maintain predictable state updates because every state change is explicitly described.


3. Reducers

Reducers are pure functions responsible for determining how the state should change in response to specific actions.

They receive the current state and an action object, then return a brand-new updated state.

Reducers never perform API calls, side effects, or asynchronous operations directly because their behavior must remain predictable and consistent.

Example:

function cartReducer(state = [], action) {

  switch (action.type) {

    case "cart/addItem":
      return [...state, action.payload];

    default:
      return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

Here:

  • cartReducer is the reducer function
  • state = [] sets default cart state
  • action receives dispatched action object
  • switch(action.type) checks action type
  • "cart/addItem" handles cart item addition
  • [...state, action.payload] creates new updated array
  • default returns existing state if no match exists

Reducers are one of the most important parts of Redux because they define how state transitions happen in a predictable way.


4. Dispatch

Dispatch acts as the communication bridge between components and the Redux store.

Whenever a user performs an action like clicking a button, submitting a form, or updating data, components dispatch actions to Redux.

Redux then forwards those actions to reducers, which calculate the next state update.

Example:

dispatch({
  type: "cart/addItem",
  payload: product
});
Enter fullscreen mode Exit fullscreen mode

Here:

  • dispatch() sends action to Redux store
  • type identifies the operation
  • payload contains product data
  • Reducer receives this action
  • Redux updates state automatically

5. Selectors

Selectors are used to read data from the Redux store.

They help components subscribe only to the specific pieces of state they need.

This improves performance because components re-render only when the selected state changes.

Example:

const cartItems = useSelector(
  (state) => state.cart
);
Enter fullscreen mode Exit fullscreen mode

Here:

  • useSelector accesses Redux state
  • state represents complete Redux store
  • state.cart extracts cart data
  • cartItems stores extracted values

Selectors help keep components clean and focused by separating state access logic from UI rendering logic.


Redux Data Flow

Redux follows a strict one-way data flow architecture.

The flow looks like this:

  1. User interacts with UI
  2. Action gets dispatched
  3. Reducer processes action
  4. Store updates state
  5. UI re-renders automatically

This predictable structure makes applications easier to debug, scale, and maintain because every state update follows the same consistent flow.


What is Redux Toolkit?

Redux Toolkit is the official recommended way to use Redux today.

Earlier Redux implementations required developers to manually create:

  • Action types
  • Action creators
  • Reducers
  • Immutable update logic
  • Middleware configuration
  • Redux DevTools setup

This resulted in large amounts of repetitive boilerplate code.

Redux Toolkit was introduced to solve these problems and provide a modern, standardized, and simplified Redux development experience.

Benefits of Redux Toolkit:

  • Less code
  • Cleaner structure
  • Built-in best practices
  • Better readability
  • Simpler async handling
  • Automatic DevTools setup
  • Easier state updates

Redux Toolkit dramatically improves developer productivity while still following Redux principles internally.


Installing Redux Toolkit

npm install @reduxjs/toolkit react-redux
Enter fullscreen mode Exit fullscreen mode
  • @reduxjs/toolkit installs Redux Toolkit
  • react-redux connects Redux with React
  • Both packages are required for React Redux applications

Creating a Redux Slice

A slice is one of the most important concepts in Redux Toolkit.

A slice groups together:

  • State
  • Reducers
  • Actions

This feature-based structure keeps Redux code cleaner and easier to scale.

Create counterSlice.js

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

const initialState = {
  value: 0
};

const counterSlice = createSlice({

  name: "counter",

  initialState,

  reducers: {

    increment: (state) => {
      state.value += 1;
    },

    decrement: (state) => {
      state.value -= 1;
    },

    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

export const {
  increment,
  decrement,
  incrementByAmount
} = counterSlice.actions;

export default counterSlice.reducer;
Enter fullscreen mode Exit fullscreen mode
  • createSlice creates Redux slice
  • initialState stores default values
  • value: 0 initializes counter
  • name: "counter" defines slice name
  • reducers contains state update functions
  • increment increases counter value
  • decrement decreases counter value
  • incrementByAmount updates value dynamically
  • action.payload receives custom value
  • counterSlice.actions auto-generates actions
  • export default counterSlice.reducer exports reducer

Slices significantly reduce Redux boilerplate and organize related logic in one place.


Why Direct Mutation Works Here

Inside Redux Toolkit:

state.value += 1;
Enter fullscreen mode Exit fullscreen mode

At first glance, this looks like direct state mutation.

In traditional Redux, directly mutating state is strictly forbidden because Redux depends on immutable updates to detect state changes correctly.

However, Redux Toolkit internally uses a library called Immer.

Immer allows developers to write code that appears mutable while safely generating immutable updates behind the scenes.

So even though the syntax looks like mutation:

state.value += 1;
Enter fullscreen mode Exit fullscreen mode

Redux Toolkit still preserves immutability internally.

This makes Redux code much cleaner and easier to write.


Configuring Redux Store

The Redux store holds the complete application state.

Create store.js

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

import counterReducer from "./counterSlice";

export const store = configureStore({

  reducer: {
    counter: counterReducer
  }
});
Enter fullscreen mode Exit fullscreen mode
  • configureStore creates Redux store
  • counterReducer imports reducer
  • reducer object combines reducers
  • counter becomes state key
  • Entire application now accesses centralized state

Redux Toolkit’s configureStore also automatically enables useful development features like Redux DevTools and middleware support.


Connecting Redux to React

To make Redux available throughout the React application, React Redux provides a component called Provider.

Inside main.jsx

import React from "react";
import ReactDOM from "react-dom/client";

import App from "./App";

import { Provider } from "react-redux";

import { store } from "./store";

ReactDOM.createRoot(
  document.getElementById("root")
).render(

  <Provider store={store}>
    <App />
  </Provider>
);
Enter fullscreen mode Exit fullscreen mode
  • Provider connects React with Redux
  • store={store} passes Redux store globally
  • <App /> gets access to Redux state
  • Every child component can now use Redux hooks

Without Provider, React components cannot access the Redux store.


Using Redux Inside Components

React Redux provides hooks that allow components to interact with Redux easily.

import React from "react";

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

import {
  increment,
  decrement
} from "./counterSlice";

function Counter() {

  const count = useSelector(
    (state) => state.counter.value
  );

  const dispatch = useDispatch();

  return (
    <div>

      <h1>{count}</h1>

      <button
        onClick={() => dispatch(increment())}
      >
        Increment
      </button>

      <button
        onClick={() => dispatch(decrement())}
      >
        Decrement
      </button>

    </div>
  );
}

export default Counter;
Enter fullscreen mode Exit fullscreen mode
  • useSelector reads Redux state
  • state.counter.value accesses counter value
  • count stores extracted state
  • useDispatch returns dispatch function
  • dispatch(increment()) triggers increment action
  • dispatch(decrement()) triggers decrement action
  • UI automatically updates after state changes

This hook-based approach makes Redux integration significantly cleaner in modern React applications.


Recommended Redux Folder Structure

As Redux applications grow, organizing files properly becomes very important.

A feature-based structure improves scalability and maintainability.

src/

├── app/
│   └── store.js

├── features/
│   ├── counter/
│   │   ├── counterSlice.js
│   │   └── Counter.jsx

├── components/
Enter fullscreen mode Exit fullscreen mode
  • app/ contains global store setup
  • features/ stores feature-based Redux logic
  • counter/ contains counter-related files
  • components/ stores reusable UI components

This structure keeps Redux logic modular and easier to scale in large applications.


Async Operations with createAsyncThunk

Real-world applications rarely work with static data.

Most applications fetch information from APIs such as:

  • User data
  • Product lists
  • Orders
  • Notifications
  • Dashboard analytics

Handling asynchronous operations manually in Redux can become complicated.

Redux Toolkit simplifies async logic using createAsyncThunk.

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

export const fetchUsers =
  createAsyncThunk(

    "users/fetchUsers",

    async () => {

      const response = await fetch(
        "https://jsonplaceholder.typicode.com/users"
      );

      return response.json();
    }
  );
Enter fullscreen mode Exit fullscreen mode
  • createAsyncThunk handles async logic
  • "users/fetchUsers" defines action name
  • async() performs API request
  • fetch() calls backend API
  • response.json() converts response into JSON
  • Returned data becomes payload automatically

createAsyncThunk also automatically generates loading, success, and error action types internally.


Redux DevTools

Redux DevTools is one of Redux’s most powerful debugging features.

It helps developers:

  • Track dispatched actions
  • Inspect state updates
  • Debug application flow
  • Replay state changes
  • Analyze application behavior

Redux Toolkit automatically enables Redux DevTools during development, making debugging significantly easier.


Redux vs Context API

React Context API can also handle shared state.

However, Redux provides several advanced capabilities that become valuable in larger applications.

Redux offers:

  • Better debugging tools
  • Middleware support
  • Predictable architecture
  • Advanced async handling
  • Improved scalability
  • Better developer tooling

Context API works well for:

  • Theme switching
  • Authentication state
  • Language preferences
  • Small shared states

Redux becomes more powerful when applications grow larger and state management complexity increases.


Common Redux Mistakes

Direct State Mutation

Wrong:

state.items.push(item);
Enter fullscreen mode Exit fullscreen mode

Chain-by-chain breakdown:

  • Direct array mutation occurs
  • Original state gets modified
  • Traditional Redux does not allow this
  • Mutations can break predictable state updates

Correct:

return [...state, item];
Enter fullscreen mode Exit fullscreen mode
  • Creates new array
  • Keeps original state immutable
  • Redux state updates safely

Understanding immutability is one of the most important concepts in Redux architecture.


RTK Query

RTK Query is one of the most powerful features introduced in Redux Toolkit.

It eliminates much of the manual API handling logic developers traditionally wrote themselves.

RTK Query automatically manages:

  • Data fetching
  • Caching
  • Loading states
  • Error handling
  • Background refetching
  • Request deduplication

This significantly reduces boilerplate and improves application performance.

Example:

import {
  createApi,
  fetchBaseQuery
} from "@reduxjs/toolkit/query/react";

export const api = createApi({

  reducerPath: "api",

  baseQuery: fetchBaseQuery({
    baseUrl: "https://api.example.com"
  }),

  endpoints: (builder) => ({

    getUsers: builder.query({

      query: () => "/users"
    })
  })
});

export const {
  useGetUsersQuery
} = api;
Enter fullscreen mode Exit fullscreen mode
  • createApi creates API service
  • reducerPath defines Redux key
  • fetchBaseQuery sets base API URL
  • endpoints defines API operations
  • builder.query() creates GET request
  • query() returns endpoint path
  • useGetUsersQuery becomes auto-generated hook

RTK Query dramatically simplifies server-state management in modern React applications.


Best Practices

Use Redux Toolkit

Redux Toolkit is now the official standard for Redux development because it reduces boilerplate, improves readability, and includes built-in best practices.


Keep State Minimal

Only store data globally if multiple components truly need access to it.

Avoid storing unnecessary UI state inside Redux.


Use Feature-Based Structure

Keep related Redux logic together inside feature folders.

This improves scalability and maintainability.


Avoid Overusing Redux

Not every piece of state belongs in Redux.

Local UI states like:

  • Modal visibility
  • Form inputs
  • Dropdown toggles
  • Tooltip visibility

are usually better managed using useState.


Final Thoughts

This was Episode 12 of the “Let’s Master React Hooks Together” series, and Redux is one of the most important concepts to understand once React applications begin scaling.

Redux may initially feel complex because it introduces several new architectural concepts. However, once you understand the predictable flow between actions, reducers, and the store, managing large-scale React applications becomes significantly more organized and maintainable.

Modern Redux with Redux Toolkit has dramatically simplified the developer experience, making Redux cleaner, easier to learn, and far more practical for real-world applications.

The goal is not just learning Redux syntax, but understanding how scalable frontend architecture works in professional applications.

Once you become comfortable with slices, reducers, actions, selectors, and async handling, building complex applications becomes significantly easier and more structured.

In the upcoming episodes, we’ll continue exploring advanced React concepts, scalable frontend architecture, performance optimization patterns, and production-level development practices used in modern applications.

See you in the next episode of “Let’s Master React Hooks Together.”

Top comments (0)