Action Creators, Payload, and the Subscribe Pattern
Four patterns that complete pure Redux before you touch React: subscribe for automatic state listening, payload for dynamic data, action type constants, and action creator functions.
The previous posts built a working Redux store. Every dispatch was hardcoded. Every action carried no data. Every state change was manually printed after each call.
None of that survives contact with a real application.
This post covers the four patterns that close those gaps before Redux connects to actual React components.
[!TIP] Run this yourself: The complete example from this post — action type constants, action creators, subscribe, and payload — is in the code-practice repo. Run
node 01-redux-core/index.jsto see every state change printed live.
Subscribe: Automatic State Listening
Right now, seeing what changed after a dispatch means writing console.log(store.getState()) after every single call. That is noise that scales badly.
store.subscribe registers a callback that runs automatically after every state change:
store.subscribe(() => {
console.log('Updated state:', store.getState())
})
store.dispatch({ type: 'ANSWER_CORRECT' })
// Updated state: 10
store.dispatch({ type: 'ANSWER_CORRECT' })
// Updated state: 20
store.dispatch({ type: 'ANSWER_WRONG' })
// Updated state: 15The callback fires once per dispatch call, after the reducer returns the new state. You do not need to trigger it manually. The store handles it.
Why this matters: subscribe is the primitive that lets UI frameworks know to re-render. React-Redux's useSelector is built on top of this mechanism. Understanding subscribe explains how the connection between Redux state and React components actually works.
Payload: Sending Data with Actions
Fixed amounts hardcoded into reducer cases are a toy constraint. Real apps need dynamic data.
The payload field carries that data. It is just a property on the action object. There is nothing special about the name payload except that it is the industry-standard convention:
store.dispatch({ type: 'ANSWER_CORRECT', payload: { points: 20 } })
store.dispatch({ type: 'ANSWER_CORRECT', payload: { points: 5 } })
store.dispatch({ type: 'ANSWER_WRONG', payload: { points: 10 } })The reducer receives the full action object as its second argument. Whatever you put in payload is available via action.payload:
function scoreReducer(state = 0, action) {
switch (action.type) {
case 'ANSWER_CORRECT':
return state + action.payload.points
case 'ANSWER_WRONG':
return state - action.payload.points
default:
return state
}
}The state progression for those three dispatches: 0 → 20 → 25 → 15.
payload can be any value: a number, a string, an object. Whatever the reducer needs to compute the next state.
Action Type Constants: No More Typos
Every dispatch and every switch case currently uses the same hardcoded string: 'ANSWER_CORRECT', 'ANSWER_WRONG'. The problem is that strings are invisible to the compiler.
A typo like 'ANSWER_CORREKT' in a dispatch call will not throw an error. The action just silently falls through to the default case and nothing happens. These bugs are painful to trace.
The fix: declare each action type as a constant and reference the constant everywhere:
const ANSWER_CORRECT = 'ANSWER_CORRECT'
const ANSWER_WRONG = 'ANSWER_WRONG'
function scoreReducer(state = 0, action) {
switch (action.type) {
case ANSWER_CORRECT:
return state + action.payload.points
case ANSWER_WRONG:
return state - action.payload.points
default:
return state
}
}
store.dispatch({ type: ANSWER_CORRECT, payload: { points: 20 } })
store.dispatch({ type: ANSWER_WRONG, payload: { points: 10 } })Now if you rename the constant, say from ANSWER_CORRECT to CORRECT_ANSWER, every reference updates in one place. The switch case, every dispatch call, all of it. The string value can change independently of the constant name without touching anything else.
Name the constant in UPPER_SNAKE_CASE. That is the naming convention for action types across the entire Redux ecosystem.
Action Creators: Functions That Build Actions
Dispatching action objects inline has a reliability problem: you have to remember every required property every time. Miss payload, or forget a field inside it, and the reducer silently does the wrong thing.
An action creator is a function that builds and returns the action object. You call the function instead of constructing the object inline:
function answerCorrect(points) {
return {
type: ANSWER_CORRECT,
payload: { points },
}
}
function answerWrong(points) {
return {
type: ANSWER_WRONG,
payload: { points },
}
}Now dispatch looks like this:
store.dispatch(answerCorrect(20))
store.dispatch(answerCorrect(5))
store.dispatch(answerWrong(10))The function accepts the dynamic data as a parameter and places it correctly in the payload. The action type is set once inside the function. You cannot dispatch this action with a missing type or a misspelled payload field because the function handles the construction.
The guarantee: anyone calling answerCorrect(20) always gets back a correctly shaped action. The shape is defined once. All callers benefit automatically.
ExpandRedux action creator flow
Everything Together
Here is the complete pattern: subscribe, payload, action type constants, action creators:
import { createStore } from 'redux'
const ANSWER_CORRECT = 'ANSWER_CORRECT'
const ANSWER_WRONG = 'ANSWER_WRONG'
function answerCorrect(points) {
return { type: ANSWER_CORRECT, payload: { points } }
}
function answerWrong(points) {
return { type: ANSWER_WRONG, payload: { points } }
}
function scoreReducer(state = 0, action) {
switch (action.type) {
case ANSWER_CORRECT: return state + action.payload.points
case ANSWER_WRONG: return state - action.payload.points
default: return state
}
}
const store = createStore(scoreReducer)
store.subscribe(() => console.log('Score:', store.getState()))
store.dispatch(answerCorrect(20)) // Score: 20
store.dispatch(answerCorrect(5)) // Score: 25
store.dispatch(answerWrong(10)) // Score: 15This is complete, idiomatic, pre-toolkit Redux. The data flow cycle (action, dispatch, store, reducer, new state) is the same. Action creators and payload are just the practical layer that makes that cycle safe and maintainable at scale.
Next: connecting this Redux store to actual React components using react-redux.
The Essentials
store.subscribe(callback)fires the callback automatically after every state change. It is the primitive that React-Redux builds on to re-render components.payloadis the convention for carrying dynamic data in an action object. The reducer reads it viaaction.payload.- Action type constants (UPPER_SNAKE_CASE) prevent silent string typo bugs. Reference the constant everywhere: in the reducer and in every dispatch call.
- Action creators are functions that build and return action objects. They enforce the correct shape in one place so every caller is guaranteed a valid action.
Further Reading and Watching
- Redux Fundamentals, Part 3: State, Actions, and Reducers: official walkthrough covering action types, payload, and action creators
- Redux Style Guide: Use Action Creators: why the Redux team recommends action creators even in simple apps
Practice what you just read.
Keep reading