Technology handbook
Redux
Predictable state transitions, actions, reducers, selectors, middleware, React integration and legacy migration.
- Topics
- 34
- Sections
- 5
Hooks
useStore
Reach the store instance for rare imperative work without subscribing to any state.
- useStore returns the Provider's Redux store instance.
- Calling getState does not subscribe the component.
- Prefer useSelector for values that must update the UI.
useSelector equality
Pass shallowEqual or a custom comparison for selections that return objects or arrays.
- The default comparison checks selected result identity.
- Use shallowEqual for suitable flat object selections.
- A custom comparator must cover every rendered field.
Selector identity in hooks
A selector returning a fresh object re-renders on every action, whatever the values inside it.
- Returning a fresh object defeats default equality checking.
- Select primitives separately or memoize object derivations.
- Keep selector inputs stable across unrelated dispatches.
Stable dispatch identity
dispatch never changes, which is why it is safe to list in a dependency array.
- The dispatch reference is stable while the store instance is unchanged.
- Include dispatch in effect and callback dependency lists.
- Replacing the Provider's store can change that reference.
Selecting inside list rows
Select one item by id in the row component rather than the whole collection in the parent.
- Pass a record ID to each connected row.
- Select that record instead of subscribing to all entities.
- Use stable list keys to preserve row identity.
Hooks versus connect
What replaced mapStateToProps, and why each hook consumer subscribes on its own.
- Hooks read context and subscribe within function components.
- connect wraps components with selected props and dispatch bindings.
- Test behavior during incremental migration between bindings.
Reading state in handlers
Prefer a thunk or getState for a value needed only at click time, not a new subscription.
- Use getState inside a thunk for the latest store snapshot.
- Do not assume a captured render value has updated after dispatch.
- Subscribe only when a value affects rendered output.
Hooks and the store Provider
Every hook resolves the store from context, so a missing Provider fails at the first read.
- Mount Provider above every React Redux hook consumer.
- Pass the correct store when testing an isolated subtree.
- Separate Providers can intentionally expose separate state domains.
Redux core
Single state tree
Model shared application state with clear domains rather than treating every component value as global.
- Organize shared state into meaningful domain slices.
- Keep derived values out of the canonical state tree.
- Leave temporary form edits local when sharing is unnecessary.
Actions and action creators
Describe events with serializable payloads and centralize their creation when useful.
- Actions need a type identifying the event being described.
- Put event data in a predictable serializable payload.
- Action creators standardize payload construction for callers.
Pure reducers
Compute next state from previous state and action without I/O, randomness or mutation.
- Return the next state without changing the previous state.
- Move network requests and other effects into middleware or thunks.
- Return the existing reference for irrelevant actions.
dispatch and subscribe
Understand action delivery and how consumers observe committed state changes.
- Dispatch a plain action to run the reducer synchronously.
- Subscribers read the committed state through getState.
- Middleware can extend dispatch to support other input forms.
combineReducers
Compose independently owned state slices under a predictable root state shape.
- Each slice reducer owns one key of the root state.
- Provide a valid initial value when state is undefined.
- Return unchanged slice state for actions it does not handle.
Immutable update patterns
Copy only changed paths and retain unchanged references to support efficient comparisons.
- Copy every ancestor of a changed nested value.
- Reuse untouched branches to preserve useful identity checks.
- Array copying alone does not copy the objects inside it.
Redux data modeling
Normalized state
Store entities by ID and reference them instead of duplicating nested records.
- Keep entities in an ID-indexed lookup with ordered IDs.
- Reference shared entities by ID instead of duplicating records.
- Update a record once so all views use consistent data.
Selectors
Encapsulate state shape and derive values without forcing every caller to know internal layout.
- Hide state layout behind named selector functions.
- Accept state and optional arguments as explicit inputs.
- Keep selectors deterministic and free of dispatch side effects.
Memoized selectors
Cache expensive derivations when their input references are unchanged.
- Use stable input selectors for expensive derived calculations.
- Recompute only when the relevant inputs change.
- Choose cache scope carefully for parameterized list selectors.
State machines
Represent valid loading and workflow states explicitly rather than using contradictory booleans.
- Represent mutually exclusive states with an explicit status field.
- Define allowed transitions for pending, success and failure.
- Prevent impossible combinations of independent loading flags.
Event-style actions
Name what happened instead of exposing implementation-level field setters everywhere.
- Describe domain events such as orderSubmitted.
- Let multiple slices respond to the same meaningful event.
- Avoid exposing every internal field as a public setter.
Local versus global state
Keep temporary UI state local unless sharing or lifecycle requirements justify global ownership.
- Keep transient input and hover state close to their component.
- Share state when distant consumers require coordinated updates.
- Consider reset lifetime before promoting data to the store.
Middleware & async
Middleware pipeline
Wrap dispatch to observe or transform actions before reducers receive them.
- Middleware wraps dispatch before actions reach reducers.
- Call next to continue through the remaining middleware chain.
- Keep middleware ordering explicit when dependencies interact.
Thunk middleware
Run async logic that can dispatch actions and read current state through an explicit function contract.
- A thunk receives dispatch and getState as arguments.
- Dispatch lifecycle actions around asynchronous operations.
- Return a promise when callers need to await completion.
Async request lifecycle
Track pending, fulfilled and rejected outcomes with request identity to handle races.
- Track request identity along with pending and error state.
- Accept completion only for the still-relevant request.
- Distinguish canceled requests from user-visible failures.
Store enhancers
Distinguish altering store capabilities from intercepting actions with middleware.
- An enhancer wraps store creation and may alter store capabilities.
- Middleware focuses on intercepting dispatched values.
- Compose enhancers in a documented order when maintaining legacy setup.
Persistence and rehydration
Version stored state and avoid treating old cached data as permanently valid.
- Persist only approved durable state fields.
- Version and migrate stored snapshots before using them.
- Revalidate remote data after restoring an old cache.
Redux DevTools
Inspect action history and state differences while understanding the limits of replaying side effects.
- Inspect dispatched events and the state changes they produced.
- Time travel replays state transitions, not external side effects.
- Sanitize action and state data before sharing debugging sessions.
React Redux & migration
Provider
Expose the intended store instance to a React subtree without cross-request state leakage.
- Provide one stable store instance to the intended React subtree.
- Create request-scoped instances for server-rendered user state.
- Initialize test Providers with realistic state fixtures.
useSelector
Subscribe to selected values and keep reference behavior compatible with equality checking.
- Select only the fields the component actually renders.
- Keep selected object identities stable when values are unchanged.
- Handle missing records when parent and child updates overlap.
useDispatch
Dispatch typed application actions and thunks from UI event handlers.
- Get dispatch from the same Provider as the component's selectors.
- Send actions from handlers rather than during render.
- Use application dispatch types when dispatching thunks.
connect and mapStateToProps
Understand the older React binding when maintaining existing applications.
- mapStateToProps converts store state into component props.
- mapDispatchToProps exposes event-oriented action callbacks.
- Keep prop derivations stable to avoid needless wrapper updates.
Typed hooks
Define reusable hooks with application state and dispatch types.
- Infer RootState and AppDispatch from the configured store.
- Export pre-typed selector and dispatch wrappers once.
- Import those wrappers instead of repeating casts in components.
Reducer and integration tests
Test meaningful state transitions and user flows instead of relying only on mocked dispatch calls.
- Verify reducer transitions from meaningful starting states.
- Render connected components with a real test store.
- Assert visible outcomes of actions and asynchronous flows.
Legacy createStore migration
Move legacy setup toward configureStore without rewriting all reducers at once.
- Replace store creation with configureStore first.
- Keep existing reducers while checking middleware compatibility.
- Resolve serialization warnings before removing safeguards.
Redux Toolkit adoption
Replace repetitive action and reducer boilerplate with slices and supported async patterns.
- Migrate one domain to createSlice at a time.
- Replace handwritten request boilerplate with supported Toolkit APIs.
- Keep action behavior compatible while updating consumers.
Sources
- Redux official documentation