What are actions, reducers, and the store in Redux?
Updated Feb 20, 2026
Short answer
In Redux, actions describe what happened, reducers define how the state changes in response to those actions, and the store holds the application's entire state and coordinates updates. Components dispatch actions to the store, the store sends them to reducers, and reducers return a new state that the store saves.
Deep explanation
Redux is a state management library based on a predictable data flow pattern. It helps applications manage shared state by enforcing a single direction in which data changes.
The three core concepts are:
- Actions: Plain JavaScript objects that describe an event or intention to change state.
- Reducers: Pure functions that calculate the next state based on the current state and an action.
- Store: The object that contains the application's state, allows dispatching actions, and notifies subscribers when state changes.
The typical Redux flow looks like this:
UI Component | | dispatch(action) v Redux Store | | sends action + current state v Reducer | | returns new state vUpdated Store State | vUI re-rendersActions
An action is a description of something that happened. It does not directly modify state.
A Redux action is usually a plain object with a type property:
{ type: "cart/itemAdded", payload: { id: 1, name: "Laptop" }}The type tells reducers what event occurred. The optional payload contains additional data needed to update the state.
Actions are often created using action creator functions:
function addItem(item) { return { type: "cart/itemAdded", payload: item };}A component can dispatch this action:
dispatch(addItem(product));The important idea is that actions represent events, not instructions. An action says "a user added an item," not "change the cart array at index 3."
Reducers
A reducer is a function that receives:
- The current state
- An action
and returns the new state.
Example:
function cartReducer(state = [], action) { switch (action.type) { case "cart/itemAdded": return [ ...state, action.payload ];
default: return state; }}Reducers must follow important rules:
- They should be pure functions.
- They should not modify the existing state directly.
- They should return a new state object when changes are needed.
- They should produce the same output for the same input.
For example, this is incorrect:
state.items.push(action.payload);return state;It mutates the existing state.
Instead, Redux expects immutable updates:
return { ...state, items: [ ...state.items, action.payload ]};Immutable updates make it easier for Redux to detect changes and support debugging features such as time-travel debugging.
Store
The Redux store is the central object that holds the application's state.
A store is created by combining reducers:
const store = createStore(rootReducer);The store provides methods such as:
store.dispatch(action);store.getState();store.subscribe(listener);Their purposes are:
dispatch(action)— sends an action to Redux.getState()— reads the current state.subscribe(listener)— runs a function whenever the state changes.
A simplified example:
store.dispatch({ type: "counter/increment"});
console.log(store.getState());The store does not contain business logic for changing data. It delegates that responsibility to reducers.
Why Redux separates these concepts
This separation provides several benefits:
- Predictability: Every state change follows the same pattern.
- Debugging: Developers can inspect every action that changed the state.
- Testing: Reducers can be tested independently because they are pure functions.
- Maintainability: Large applications can organize state changes into separate reducers.
Modern Redux applications commonly use Redux Toolkit, which simplifies writing actions and reducers while keeping Redux's core principles.
Real-world example
Imagine an e-commerce application with a shopping cart.
The user clicks "Add to Cart."
1. Component dispatches an action
dispatch({ type: "cart/addProduct", payload: { id: 101, name: "Keyboard", quantity: 1 }});2. Reducer handles the action
function cartReducer(state = { items: [] }, action) { switch (action.type) { case "cart/addProduct": return { ...state, items: [ ...state.items, action.payload ] };
default: return state; }}3. Store updates the state
Before:
{ items: []}After:
{ items: [ { id: 101, name: "Keyboard", quantity: 1 } ]}4. UI receives the updated state
A cart icon might automatically update from:
Cart (0)to:
Cart (1)The component never directly changed the cart data. It only dispatched an action, and Redux handled the state transition.
Common mistakes
- * Confusing actions with state changes — actions describe events but do not directly update state.
- * Mutating state inside reducers instead of returning new state objects.
- * Putting API calls, timers, or other side effects directly inside reducers.
- * Creating multiple Redux stores unnecessarily instead of keeping one central store.
- * Forgetting to handle unknown actions by returning the existing state.
- * Treating the store as a place for all application data even when local component state would be simpler.
- * Writing reducers that depend on external variables, making them unpredictable and difficult to test.
Follow-up questions
- Why must Redux reducers be pure functions?
- What happens when an action is dispatched in Redux?
- What is the difference between Redux state and React component state?
- Why does Redux use immutable state updates?
- What are middleware and why are they used in Redux?
- How does Redux Toolkit improve traditional Redux usage?