Redux in React – Store, Reducers, and Actions (Step-by-Step Guide)

Redux is a predictable state container for JavaScript applications.
It is commonly used with React to manage global state, making your app more scalable and maintainable.

Key concepts in Redux:

  1. Store – Holds the application state

  2. Reducers – Functions that update state based on actions

  3. Actions – Descriptions of what should happen in the state

Redux enforces a unidirectional data flow, keeping your state predictable and easier to debug.

Redux Store

The store is the single source of truth for your app’s state.
It holds the current state object and provides methods to read state, dispatch actions, and subscribe to updates.

Creating a Redux store:

import { createStore } from "redux";
import { counterReducer } from "./counterReducer";

const store = createStore(counterReducer);

export default store;

Store Methods:

MethodPurpose
getState()Returns current state
dispatch(action)Updates state by sending an action
subscribe(listener)Listen for state changes

Actions

Actions are plain JavaScript objects describing what happened.

Structure of an action:

 
{ type: "INCREMENT", payload: 1 }
  • type → required, describes the action

  • payload → optional, contains data for the reducer

Example Actions:

export const increment = () => ({ type: "INCREMENT" });
export const decrement = () => ({ type: "DECREMENT" });
export const reset = () => ({ type: "RESET" });

Reducers

Reducers are pure functions that take the current state and an action, and return new state.

Rules for reducers:

  • Pure function (no side effects)

  • Returns a new state object, does not mutate the existing state

  • Handles different action types

Example Counter Reducer:

export const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case "INCREMENT":
      return { count: state.count + 1 };
    case "DECREMENT":
      return { count: state.count - 1 };
    case "RESET":
      return { count: 0 };
    default:
      return state;
  }
};

Example: Counter App with Redux

Step 1: Install Redux

npm install redux react-redux

Step 2: Create Store and Reducer

// counterReducer.js
export const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case "INCREMENT": return { count: state.count + 1 };
    case "DECREMENT": return { count: state.count - 1 };
    case "RESET": return { count: 0 };
    default: return state;
  }
};
// counterReducer.js
export const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case "INCREMENT": return { count: state.count + 1 };
    case "DECREMENT": return { count: state.count - 1 };
    case "RESET": return { count: 0 };
    default: return state;
  }
};

Step 3: Connect Redux to React

import React from "react";
import ReactDOM from "react-dom";
import { Provider, useDispatch, useSelector } from "react-redux";
import { store } from "./store";

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

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
      <button onClick={() => dispatch({ type: "RESET" })}>Reset</button>
    </div>
  );
}

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

How it works:

  • store holds the state

  • dispatch sends actions to the reducer

  • useSelector reads state from the store

  • useDispatch sends actions to update state

Best Practices

  • Keep reducers pure and small

  • Use action creators for consistency

  • Split large state into multiple reducers using combineReducers

  • Use Redux DevTools for debugging state changes

  • Avoid storing derived data in the store; compute it in selectors