R

React Handbook

Clean • Professional

Redux & Redux Toolkit: Centralized State Management in React

5 minute

Redux & Redux Toolkit

When your React application grows, managing state across multiple components becomes challenging. Passing props through many layers or juggling multiple contexts can make your app hard to maintain.

That’s where Redux comes in — a powerful state management library that provides a centralized store to manage and share state across your entire application.

And with Redux Toolkit (RTK) — the official, modern, and simplified way to use Redux — you can write cleaner, faster, and less boilerplate code.

What is Redux?

Redux is a predictable state container for JavaScript applications.

It helps manage your app’s global state in a single source of truth (the store) and ensures that data flow is consistent and easy to debug.

Redux is based on three core principles:

  1. Single Source of Truth – The entire app state is stored in one centralized store.
  2. State is Read-Only – State can only be changed by dispatching an action.
  3. Changes via Pure FunctionsReducers specify how the state changes based on actions.

Why Use Redux?

Redux is useful when:

  • You need to share state between many components.
  • Your app’s data flow is complex.
  • You want predictable behavior and easy debugging.
  • You’re building a large-scale application.

Redux Architecture Overview

Redux uses a unidirectional data flow, which means data moves in one consistent direction:

Component → Action → Reducer → Store → UI

Here’s how it works:

ConceptDescription
StoreCentral place that holds the entire application state
ActionA plain JavaScript object that describes what you want to do
ReducerA pure function that takes the current state and an action, and returns a new state
DispatchA method used to send actions to the reducer
SelectorA function to read or access data from the store

Redux Toolkit (RTK) – The Modern Redux

Redux Toolkit (RTK) is the official, recommended way to write Redux logic.

It simplifies the setup process and reduces boilerplate by providing:

  • configureStore() – Simplified store setup
  • createSlice() – Combines reducers and actions in one place
  • createAsyncThunk() – Handles asynchronous logic easily
  • Built-in middleware like Redux Thunk for async API calls

Setting Up Redux Toolkit

Install the required packages:

npm install @reduxjs/toolkit react-redux

Creating a Redux Slice

A slice represents a single piece of your app’s state (like user, cart, or theme).

Example: counterSlice.js

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

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

export const { increment, decrement, reset } = counterSlice.actions;
export default counterSlice.reducer;

What happens here:

  • createSlice() automatically creates actions and reducers.
  • We don’t have to write separate files for each.
  • State updates are written in a mutable-looking way, but Redux Toolkit uses Immer.js under the hood to keep them immutable.

Creating a Centralized Store

store.js

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export default store;

configureStore() sets up:

  • Redux DevTools integration automatically
  • Default middleware for async logic and error handling

Providing the Store to React

Wrap your app with the Provider component from react-redux:

import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import App from "./App";
import store from "./store";

ReactDOM.createRoot(document.getElementById("root")).render(
  <Provider store={store}>
    <App />
  </Provider>
);

Using Redux in Components

Use two hooks from react-redux:

learn code with durgesh images

useSelector() – to access state

  • Used to read data from the Redux store.
  • It allows you to extract specific parts of the global state inside any React component.

useDispatch() – to send actions

  • Used to dispatch actions to the Redux store.
  • It gives you access to the store’s dispatch method, allowing you to update the state.

Example: Counter.js

import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, reset } from "./counterSlice";

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

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(reset())}>Reset</button>
    </div>
  );
}

export default Counter;

Handling Asynchronous Logic with createAsyncThunk

For API calls or async operations, Redux Toolkit provides createAsyncThunk().

Example:

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

export const fetchUsers = createAsyncThunk("users/fetchUsers", async () => {
  const response = await fetch("<https://jsonplaceholder.typicode.com/users>");
  return await response.json();
});

const userSlice = createSlice({
  name: "users",
  initialState: { list: [], status: "idle" },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.status = "loading";
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.status = "succeeded";
        state.list = action.payload;
      })
      .addCase(fetchUsers.rejected, (state) => {
        state.status = "failed";
      });
  },
});

export default userSlice.reducer;

This handles loading, success, and error states — perfect for async APIs.

Middleware in Redux

Middleware lets you intercept actions before they reach the reducer.

Common examples include:

  • Redux Thunk → For async actions (already built into RTK)
  • Redux Logger → For debugging actions
  • Custom Middleware → For analytics, caching, etc.

Example of custom middleware:

const logger = (store) => (next) => (action) => {
  console.log("Dispatching:", action);
  let result = next(action);
  console.log("Next state:", store.getState());
  return result;
};

You can add it in configureStore().

Redux Toolkit vs Traditional Redux

FeatureTraditional ReduxRedux Toolkit
SetupManual, verboseEasy configureStore()
ActionsSeparate filesAuto-generated
ReducersManualCreated via createSlice()
Async LogicThunk middleware setupcreateAsyncThunk() built-in
BoilerplateA lotMinimal
DevToolsManual setupAutomatic

Benefits of Redux Toolkit

  • Centralized and predictable global state
  • Built-in async handling
  • Easier debugging and testing
  • Cleaner, less repetitive code
  • Full support for TypeScript
  • Built-in middleware and DevTools

When to Use Redux

Use Redux if your app:

  • Has shared global state across many components
  • Requires complex state logic
  • Needs debugging tools or predictable updates
  • Handles async data fetching (like APIs)

If your app is small and state is simple, Context API or useState might be enough.

Summary Table

ConceptDescription
StoreCentral place for app state
SliceCombines reducer logic and actions
ActionDescribes a state change
ReducerDefines how state changes
DispatchSends actions to reducers
MiddlewareHandles async or side effects


Article 0 of 0