← Back to React Course | Chapter 9: State Management | Lesson 5 of 10

Store, Actions, and Reducers

Actions and reducers are like order tickets in a restaurant kitchen — the ticket (action) says what was ordered, and the chef (reducer) is the only one allowed to actually prepare the dish (update the state).

Anatomy of an Action

An action is just a plain JavaScript object with, at minimum, a type field — a string describing what happened, like 'cart/addItem'. Any additional data needed to process that action (like which item was added) is usually included as a payload field.

Note: A common convention is 'domain/eventName' for type strings, like 'cart/addItem' or 'auth/login', to avoid collisions between unrelated actions.

Warning: Actions only describe what happened — dispatching one doesn't change anything by itself until a reducer handles it.

Example: Anatomy of an Action

markup
// Run in your local React project (npm install required)
const addItemAction = {
  type: 'cart/addItem',
  payload: { id: 1, name: 'Book' },
};
store.dispatch(addItemAction);

Anatomy of a Reducer

A reducer is a function taking the current state and an action, and returning the next state. It must be pure — given the same inputs, it always produces the same output, with no API calls, no mutations, and no randomness.

Note: Use a switch statement (or an if/else chain) on action.type to decide which piece of logic to run.

Warning: Forgetting a default case that returns the unchanged state means an unrecognized action type could return undefined, corrupting the store.

Example: Anatomy of a Reducer

markup
// Run in your local React project (npm install required)
function cartReducer(state = { items: [] }, action) {
  switch (action.type) {
    case 'cart/addItem':
      return { items: [...state.items, action.payload] };
    default:
      return state;
  }
}

The One-Way Data Flow

Redux enforces a strict cycle: a component dispatches an action, the reducer computes new state from it, the store updates, and any component reading that state re-renders with the new value. This predictable, one-directional flow is what makes Redux apps easier to debug and reason about.

Note: Redux DevTools lets you literally see every dispatched action and the resulting state change, which is invaluable for debugging this flow.

Warning: Nothing in this cycle updates the UI directly — components only re-render because they're subscribed to the store via useSelector, reacting to the state change.

Example: The One-Way Data Flow

markup
// Run in your local React project (npm install required)
// 1. Component dispatches:
dispatch({ type: 'cart/addItem', payload: { id: 2, name: 'Pen' } });
// 2. Reducer computes new state (shown above)
// 3. Store updates internally
// 4. useSelector(state => state.cart.items) re-runs in any subscribed component
// 5. That component re-renders with the new items array
Common Mistakes
  1. Giving two different actions the same type string, causing one to accidentally trigger the other's logic.
  2. Putting side effects (API calls, timers) directly inside a reducer instead of in middleware or a component.
  3. Returning undefined from a reducer's default case instead of returning the unchanged state.
Chapter Summary
  • An action is a plain object with a type field describing what happened, plus optional data (the payload).
  • A reducer is a pure function: (state, action) => newState, with no side effects.
  • The store's dispatch(action) function is the only way to trigger a state update.
  • Reducers must always return a value, including an unchanged state for actions they don't recognize.
Browser Support

Requires npm install redux (or @reduxjs/toolkit) — not available via CDN in this sandbox.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.