Redux launched this month and is spreading quickly through the React community. It's another state management library solving similar problems as Flux, but with a radically simpler approach: three principles, one store, pure functions.
After building with it, I'm convinced Redux's constraints are its strength. But those constraints require discipline most teams don't have.
The Flux Problem
Facebook's Flux pattern addressed state management in React apps: unidirectional data flow, actions trigger updates, stores hold state. This was clearer than two-way binding chaos.
But Flux implementations were verbose. Multiple stores, dispatcher boilerplate, lots of imperative code. The pattern was right; the implementation was heavy.
Flux variations proliferated—Alt, Reflux, Flummox, Marty. Each tried different trade-offs. The fragmentation was exhausting.
Redux is Dan Abramov's take: "What if Flux had one store and reducers?"
The Three Principles
Redux's entire philosophy:
1. Single source of truth All application state lives in one object tree in one store.
{
user: { name: 'Alice', authenticated: true },
posts: [ ... ],
ui: { loading: false }
}
No multiple stores to synchronize. State is just data.
2. State is read-only The only way to change state is by dispatching actions.
dispatch({ type: 'LOGIN_SUCCESS', user: userData });
No direct mutation. This makes changes traceable and enables powerful features.
3. Changes are made with pure functions
Reducers are pure functions: (state, action) => newState
function user(state = null, action) {
switch (action.type) {
case 'LOGIN_SUCCESS':
return action.user;
case 'LOGOUT':
return null;
default:
return state;
}
}
Pure functions make testing trivial and enable time-travel debugging.
What This Enables
Redux's constraints unlock capabilities:
Time-travel debugging: Record actions, replay them, undo/redo. Since state changes are deterministic, this works reliably.
Hot reloading: Replace reducers without losing state. Development becomes instant feedback.
Server-side rendering: Serialize state, send to client, hydrate. The single store makes this straightforward.
Testability: Pure functions with no side effects are trivial to test. No mocks, no stubs, just input/output.
These aren't gimmicks. They solve real problems and change how development feels.
The Reducer Pattern
Reducers are the heart of Redux:
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.todo];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id
? { ...todo, completed: !todo.completed }
: todo
);
default:
return state;
}
}
This looks simple until you need to update deeply nested state. Immutable updates with spread operators get verbose:
return {
...state,
users: {
...state.users,
[userId]: {
...state.users[userId],
profile: {
...state.users[userId].profile,
name: newName
}
}
}
};
Libraries like Immutable.js help, but now you're learning another API. The purity is valuable but costly.
The Async Problem
Redux handles synchronous updates elegantly. Async is harder. Reducers must be pure—no side effects, no API calls.
So where do async operations go? Redux doesn't have opinions. The community created solutions:
- redux-thunk: Actions can be functions that dispatch
- redux-saga: Use generators for complex async flows
- redux-observable: Use RxJS observables
- redux-promise: Actions can be promises
Each solves async differently. None feels perfect. This is Redux's biggest weakness—the async story is fragmented.
When Redux Makes Sense
Redux works well for:
- Complex state with many updates
- State shared across many components
- Applications that benefit from time-travel debugging
- Teams comfortable with functional programming
Redux is overkill for:
- Simple applications with local component state
- Applications where state is mostly server-driven
- Teams that find immutability confusing
- Prototypes where structure overhead isn't worth it
The Boilerplate Concern
Redux's simplicity has cost: boilerplate. For each feature:
- Define action types (constants)
- Create action creators
- Write reducer cases
- Connect components
This is repetitive. The Redux team says it's worth it for explicitness and debuggability. But it feels tedious compared to frameworks with magic.
The ecosystem is creating abstractions to reduce boilerplate. Whether these help or obscure Redux's clarity is debatable.
Comparison to MobX and Other Approaches
Redux is one approach to state management. Alternatives exist:
MobX (recently released): Observable state with automatic updates. Less boilerplate, less explicit. Different philosophy.
Component state: React's setState is sufficient for many apps. Redux's overhead isn't always justified.
GraphQL + Relay: For applications with remote data, query-driven state management might be better.
Redux isn't universal. It's one well-designed tool for specific problems.
Looking Forward
Redux's rapid adoption suggests the React community was ready for this approach. Flux's pattern with simpler implementation resonates.
The async story needs consolidation. Multiple competing solutions fragment the ecosystem. Redux Saga seems promising but is complex.
Whether Redux becomes the standard or just one of many state management approaches depends on how well it handles real-world complexity at scale.
For now, it's the best answer to React state management we have. Simple API, powerful concepts, active community. Worth learning even if you don't use it—the patterns transfer.
Resources:
- Redux – Official documentation
- Getting Started with Redux – Dan Abramov's video course
- Redux GitHub – Source and examples