LMX
BlogSelectedWorkSponsorsAbout
Command Palette

Search for a command to run...

Blog

Understanding and Using Redux in React

LMXMar 17, 20198 min read#React

An introduction to Redux in React, including its workflow, core concepts—actions, reducers, and the store—and a complete example showing how to configure Redux for maintainable, extensible state management.

1. Why Use Redux?

Illustration

  1. You do not need Redux when values and method calls remain within a simple parent-child component relationship.
  2. Consider Redux when one child updates shared state—organization data, for example—and other pages also depend on that data. Moving the state into Redux gives the application one place to manage it.
2. How Redux Works

Redux process

3. Redux Workflow

Redux workflow

4. Introduction to Redux

Redux is a predictable state container for JavaScript applications.

  1. Predictable
  2. State container
  3. For JavaScript applications

Redux overview

First, understand the three core parts of the Redux model: actions, reducers, and the store.

  • Action: The payload that sends data from an application to the store. Actions are the store's only source of information. To request a state change from a local or remote component, dispatch an action.
  • Reducer: An action describes what happened but does not change state itself. A reducer handles events according to action.type and calculates the next state.
  • Store: The object connecting actions and reducers. It is effectively a state tree containing the application's state. UI components can read the state they need from it.

React components do not directly mutate state values; they request changes through setState, in part because of the Virtual DOM model. In a component tree, a parent passes values to its children through props, and children cannot mutate their own props. That is why many introductory React examples keep state in the top-level component: it owns rerendering when data changes, while child components primarily display that data.

A common communication technique is to pass a parent method to a child through props. The child calls it in response to an event, indirectly changing the parent's state. This works in a small application but becomes awkward with deeply nested components or communication across separate branches of the tree.

In a complex component tree, application data and every operation that changes it can be separated from the UI. Together they form application-level state. To distinguish this persistent data from component-local state, its container is called the store.

The preceding explanation is adapted from an imooc.com Redux summary.

5. A Simple Redux Example

The original example uses create-react-app. Before starting, make sure Node.js and your package manager are installed.

npm install -g create-react-app
create-react-app redux-shopping

Delete everything in src except index.js, then replace its contents:

import { createStore } from "redux";

const reducer = function (state, action) {
  return state;
};

const store = createStore(reducer);

This code:

  1. Imports createStore() from Redux.
  2. Defines a reducer. state is the data currently in the store; action contains a required type such as ADD, UPDATE, or DELETE, plus an optional payload carrying update data.
  3. Creates a Redux store from the reducer. Store data can be read directly but can only be updated through the reducer.

The current state is undefined. Give it an empty array as the default:

const reducer = function (state = [], action) {
  return state;
};

For multiple reducers, use Redux's combineReducers:

// src/index.js

import { createStore, combineReducers } from "redux";

const productsReducer = function (state = [], action) {
  return state;
};

const cartReducer = function (state = [], action) {
  return state;
};

const allReducers = {
  products: productsReducer,
  shoppingCart: cartReducer,
};

const rootReducer = combineReducers(allReducers);
const store = createStore(rootReducer);

Next, add some initial data:

const initialState = {
  cart: [
    { product: "bread 700g", quantity: 2, unitCost: 90 },
    { product: "milk 500ml", quantity: 1, unitCost: 47 },
  ],
};

const cartReducer = function (state = initialState, action) {
  return state;
};

const store = createStore(rootReducer);
console.log("initial state: ", store.getState());

Run npm start or yarn start, then inspect the state in the browser console.

Initial state

The cart reducer currently does nothing. To manage cart state, define an add action:

const ADD_TO_CART = "ADD_TO_CART";

const cartReducer = function (state = initialState, action) {
  switch (action.type) {
    case ADD_TO_CART:
      return {
        ...state,
        cart: [...state.cart, action.payload],
      };
    default:
      return state;
  }
};

A reducer handles different action types, commonly with a switch. When an ADD_TO_CART action is dispatched, this case calculates and returns a new state.

Now define an action creator. An action is a JavaScript object with a required type and optional payload:

function addToCart(product, quantity, unitCost) {
  return {
    type: ADD_TO_CART,
    payload: { product, quantity, unitCost },
  };
}

Subscribe to changes, then dispatch several actions:

const unsubscribe = store.subscribe(() => console.log(store.getState()));

store.dispatch(addToCart("Coffee 500gm", 1, 250));
store.dispatch(addToCart("Flour 1kg", 2, 110));
store.dispatch(addToCart("Juice 2L", 1, 250));

unsubscribe();

The complete index.js is:

import { createStore, combineReducers } from "redux";

const productsReducer = function (state = [], action) {
  return state;
};

const initialState = {
  cart: [
    { product: "bread 700g", quantity: 2, unitCost: 90 },
    { product: "milk 500ml", quantity: 1, unitCost: 47 },
  ],
};

const ADD_TO_CART = "ADD_TO_CART";

const cartReducer = function (state = initialState, action) {
  switch (action.type) {
    case ADD_TO_CART:
      return {
        ...state,
        cart: [...state.cart, action.payload],
      };
    default:
      return state;
  }
};

function addToCart(product, quantity, unitCost) {
  return {
    type: ADD_TO_CART,
    payload: { product, quantity, unitCost },
  };
}

const rootReducer = combineReducers({
  products: productsReducer,
  shoppingCart: cartReducer,
});

const store = createStore(rootReducer);
console.log("initial state: ", store.getState());

const unsubscribe = store.subscribe(() => console.log(store.getState()));

store.dispatch(addToCart("Coffee 500gm", 1, 250));
store.dispatch(addToCart("Flour 1kg", 2, 110));
store.dispatch(addToCart("Juice 2L", 1, 250));

unsubscribe();

After saving, the browser refreshes and the newly added products appear in the console.

Updated state

Splitting the Code into Modules

As index.js grows, organize it into a Redux project structure:

src/
├── actions
│   └── cart-actions.js
├── index.js
├── reducers
│   ├── cart-reducer.js
│   ├── index.js
│   └── products-reducer.js
└── store.js
// src/actions/cart-actions.js
export const ADD_TO_CART = "ADD_TO_CART";

export function addToCart(product, quantity, unitCost) {
  return {
    type: ADD_TO_CART,
    payload: { product, quantity, unitCost },
  };
}
// src/reducers/products-reducer.js
export default function (state = [], action) {
  return state;
}
// src/reducers/cart-reducer.js
import { ADD_TO_CART } from "../actions/cart-actions";

const initialState = {
  cart: [
    { product: "bread 700g", quantity: 2, unitCost: 90 },
    { product: "milk 500ml", quantity: 1, unitCost: 47 },
  ],
};

export default function (state = initialState, action) {
  switch (action.type) {
    case ADD_TO_CART:
      return {
        ...state,
        cart: [...state.cart, action.payload],
      };
    default:
      return state;
  }
}
// src/reducers/index.js
import { combineReducers } from "redux";
import productsReducer from "./products-reducer";
import cartReducer from "./cart-reducer";

export default combineReducers({
  products: productsReducer,
  shoppingCart: cartReducer,
});
// src/store.js
import { createStore } from "redux";
import rootReducer from "./reducers";

export default createStore(rootReducer);
// src/index.js
import store from "./store.js";
import { addToCart } from "./actions/cart-actions";

console.log("initial state: ", store.getState());

const unsubscribe = store.subscribe(() => console.log(store.getState()));

store.dispatch(addToCart("Coffee 500gm", 1, 250));
store.dispatch(addToCart("Flour 1kg", 2, 110));
store.dispatch(addToCart("Juice 2L", 1, 250));

unsubscribe();

The reorganized code behaves exactly as before. Now add update and delete actions.

// src/actions/cart-actions.js
export const UPDATE_CART = "UPDATE_CART";
export const DELETE_FROM_CART = "DELETE_FROM_CART";

export function updateCart(product, quantity, unitCost) {
  return {
    type: UPDATE_CART,
    payload: { product, quantity, unitCost },
  };
}

export function deleteFromCart(product) {
  return {
    type: DELETE_FROM_CART,
    payload: { product },
  };
}
// src/reducers/cart-reducer.js
export default function (state = initialState, action) {
  switch (action.type) {
    case ADD_TO_CART:
      return {
        ...state,
        cart: [...state.cart, action.payload],
      };
    case UPDATE_CART:
      return {
        ...state,
        cart: state.cart.map((item) =>
          item.product === action.payload.product ? action.payload : item,
        ),
      };
    case DELETE_FROM_CART:
      return {
        ...state,
        cart: state.cart.filter(
          (item) => item.product !== action.payload.product,
        ),
      };
    default:
      return state;
  }
}

Finally, dispatch both actions from index.js:

store.dispatch(updateCart("Flour 1kg", 5, 110));
store.dispatch(deleteFromCart("Coffee 500gm"));
6. Using Redux in a Real Project

A user dispatches an action, the reducer calculates new state, and the view rerenders. One important question remains: what about asynchronous work?

When a reducer calculates state immediately after an action is dispatched, the flow is synchronous.

In real applications, many actions begin with asynchronous API requests:

When the reducer runs later, after asynchronous work completes, the overall flow is asynchronous.

redux-thunk is a popular middleware for asynchronous Redux actions.

  1. Import thunk: import thunk from "redux-thunk".
  2. Import middleware support: import { createStore, applyMiddleware } from "redux".
  3. Create the store with createStore(reducer, applyMiddleware(thunk)).
  4. The middleware is active once passed through applyMiddleware.
  5. Action creators may now return functions, giving synchronous and asynchronous actions a consistent calling style.
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";

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

export function addGunAsync() {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(addGUN());
    }, 2000);
  };
}

For mapStateToProps, mapDispatchToProps, and connect, see:

https://www.cnblogs.com/hanmeimei/p/8820621.html

Related articles:

  • https://blog.csdn.net/xiangzhihong8/article/details/80518709
  • https://blog.csdn.net/qq_42606051/article/details/81907165

Discussion

Support the author
Previous post

Implementing the function of car trajectory query with OpenLayers

LMX BlogWeb · TypeScript · Vue · Node.js
Crafted by
@LMXZQQ
Build
unavailable
Date
2026-10-30
Articles
29
Deployed on
Nginx
Source code
GitHub
License
MIT License
Typeface
Geist
Stack
  • next@16.4.3
  • react@19.3.1
  • @base-ui/react@1.8.0
  • shadcn@4.12.0
  • tailwindcss@4.3.3
Analytics
  • OpenPanel
  • Google Analytics
For agents
  • llms.txt
  • index.md
Inspired by
  1. Tailwind CSS
  2. shadcn/ui
  3. Vercel
  4. Apple
  5. Evil Charts
  6. Devouring Details
  7. Skiper UI
  8. Making Software
  9. shadcncraft
Approved by
静水流深 / LMX
1602
Command Palette

Search for a command to run...