Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Redux Administrator & Developer Guide

Generated May 2026 from reduxjs/redux at 0d8fae6

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

1. Architecture Overview

Redux is a predictable state container for JavaScript applications. At its core, it implements a minimal, framework-agnostic state machine: a single store holds an immutable state tree, plain-object actions describe state transitions, and pure reducer functions compute the next state. The entire public API surface consists of seven functions and two utilities, totaling under 700 lines of TypeScript source.

Redux is not an application framework — it is a coordination primitive. Its value comes from the strict constraints it enforces (immutability, unidirectional data flow, pure reducers) and the ecosystem those constraints enable (time-travel debugging, middleware pipelines, hot reloading, server-side rendering).

The Redux team now recommends Redux Toolkit (@reduxjs/toolkit) as the standard way to write Redux logic. Redux Toolkit wraps this core package with configureStore, createSlice, createAsyncThunk, and other utilities. This guide documents the core redux package internals that Redux Toolkit builds upon.

Architecture Diagram

                        ┌──────────────────────┐
                        │     Application       │
                        │                       │
                        │  dispatch(action) ────┤
                        │                       │
                        │  ◄── subscribe() ─────┤
                        │  ◄── getState() ──────┤
                        └───────────┬───────────┘
                                    │
                        ┌───────────▼───────────┐
                        │        Store          │
                        │                       │
                        │  ┌─────────────────┐  │
                        │  │   Middleware     │  │
                        │  │   Chain          │  │
                        │  │  (applyMiddle-   │  │
                        │  │   ware)          │  │
                        │  └────────┬────────┘  │
                        │           │           │
                        │  ┌────────▼────────┐  │
                        │  │  Root Reducer   │  │
                        │  │  (combine-      │  │
                        │  │   Reducers)     │  │
                        │  └────────┬────────┘  │
                        │           │           │
                        │  ┌────────▼────────┐  │
                        │  │  State Tree     │  │
                        │  │  (immutable)    │  │
                        │  └─────────────────┘  │
                        │                       │
                        │  Listeners (Map)      │
                        └───────────────────────┘

Component Inventory

Component Source Path Role
createStore src/createStore.ts Store factory — holds state, manages dispatch/subscribe/getState lifecycle
combineReducers src/combineReducers.ts Merges multiple slice reducers into a single root reducer
applyMiddleware src/applyMiddleware.ts Store enhancer that composes a middleware chain around dispatch
bindActionCreators src/bindActionCreators.ts Wraps action creators with dispatch for direct invocation
compose src/compose.ts Right-to-left function composition utility
isAction src/utils/isAction.ts Type guard — checks plain object with string type
isPlainObject src/utils/isPlainObject.ts Prototype-chain check for plain objects
actionTypes src/utils/actionTypes.ts Internal action types (@@redux/INIT, REPLACE, PROBE_UNKNOWN_ACTION)
warning src/utils/warning.ts Development-only console.error with breakpoint trick
kindOf src/utils/kindOf.ts Human-readable type name for error messages (dev only)
formatProdErrorMessage src/utils/formatProdErrorMessage.ts Minified error codes with URL links for production
symbol-observable src/utils/symbol-observable.ts TC39 Symbol.observable polyfill

Technology Stack

Layer Technology
Language TypeScript 5.x (strict mode, exactOptionalPropertyTypes)
Build tsup (esbuild-based, 4 output formats)
Test Vitest (runtime + type-level tests via --typecheck)
Lint ESLint with @typescript-eslint
Format Prettier (no semicolons, single quotes, trailing comma: none)
Package Manager Yarn 4.4.1 (PnP disabled, nodeLinker: node-modules)
CI/CD Netlify (website), npm publish
Documentation Site Docusaurus 3.6.3 (in website/)

Data Flow

  1. Application calls store.dispatch(action) with a plain object
  2. If middleware is applied, the action passes through the middleware chain (each middleware can inspect, modify, delay, or replace the action)
  3. The innermost middleware calls the original store.dispatch, which:
    • Validates the action is a plain object with a string type
    • Calls currentReducer(currentState, action) synchronously
    • Replaces currentState with the return value
    • Iterates all registered listeners (Map-based, snapshot before dispatch)
  4. The dispatched action object is returned to the caller

Note

The listener collection uses a Map<number, ListenerCallback> with numeric IDs, not an array. Subscriptions and unsubscriptions during a dispatch take effect on the next dispatch, not the current one — the listener list is snapshotted via shallow copy before iteration.

2. System Requirements

Runtime Requirements

Requirement Detail
JavaScript Engine Any ES2015+ environment (all modern browsers, Node.js 14+)
Required Globals Map, Object.getPrototypeOf, Symbol (for observable interop)
Dependencies None — zero runtime dependencies

Development Requirements

Requirement Version
Node.js 14+ (20 for website build)
Yarn 4.4.1 (ships via .yarnrc.yml → .yarn/releases/yarn-4.4.1.cjs)
TypeScript 5.8.2+

Build Output Sizes

The library is extremely small. The sideEffects: false flag enables complete tree-shaking for ESM consumers.

3. Configuration Reference

Redux has no configuration files. All configuration is code-level, passed to createStore at instantiation.

createStore Parameters

Parameter Type Required Description
reducer Reducer<S, A> Yes Root reducer function: (state, action) => newState
preloadedState S No Initial state for hydration (SSR) or restored sessions
enhancer StoreEnhancer No Store enhancer (e.g., applyMiddleware(...), composed enhancers)

Important

If preloadedState is omitted and the second argument is a function, Redux treats it as the enhancer. This enables the two-argument form: createStore(reducer, enhancer).

combineReducers Behavior

Aspect Detail
Input Object mapping keys to reducer functions
Validation Calls each reducer with undefined state + @@redux/INIT action; throws if any returns undefined
Probing Calls each reducer with undefined state + random action type; throws if any returns undefined
Unknown keys In development, logs a warning if state contains keys with no matching reducer
Reference equality Returns the same state object if no slice reducer returns a new value

applyMiddleware Behavior

Aspect Detail
Input Variadic list of middleware functions
Middleware API Each middleware receives { getState, dispatch } — note: dispatch is the enhanced dispatch, not the original
Chain construction compose(...middlewares.map(m => m(middlewareAPI)))(store.dispatch)
Return A StoreEnhancer that wraps createStore

Environment Variables

Variable Scope Effect
NODE_ENV Build-time production: strips dev warnings, uses formatProdErrorMessage with numeric codes. development: enables kindOf() detailed type names, combineReducers shape warnings, console.error warnings.

Build Configuration

The build is configured via tsup.config.ts:

Output Format Minified NODE_ENV File
Standard ESM esm No Embedded checks dist/redux.mjs
Legacy ESM esm (ES2017 target) No Embedded checks dist/redux.legacy-esm.js
Browser ESM esm Yes production (hardcoded) dist/redux.browser.mjs
CJS cjs No Embedded checks dist/cjs/redux.cjs
Type Declarations — — — dist/redux.d.mts

All builds use sourcemaps. The mangleErrorsTransform Babel plugin replaces verbose error strings with numeric codes (mapped in errors.json) for production builds.

TypeScript Configuration

Three tsconfig files serve different purposes:

Config Extends Purpose
tsconfig.base.json — Shared settings: strict, exactOptionalPropertyTypes, isolatedModules, path aliases (@internal/* → ./src/*, redux → ./src/index.ts)
tsconfig.build.json base Library build: emitDeclarationOnly, outputs to dist/
tsconfig.test.json base Type-level tests: noEmit, includes test/typescript/

Note

The redux path alias in tsconfig.base.json is annotated // @remap-prod-remove-line — it is stripped during production builds so tests can import from the source directly while published types point to the built output.

4. Deployment & Scaling

Publishing

The package is published to npm as redux. The publish lifecycle:

  1. prepublishOnly: clean → format check → lint → test
  2. prepack: clean → tsup build (4 output formats + types)
  3. npm publish uploads dist/ and src/ (per "files" in package.json)

Release Management

Releases use release-it (configured in .release-it.json):

{
  "hooks": {
    "after:bump": "yarn && git add -u"
  },
  "git": {
    "tagName": "v${version}"
  }
}

Git tags follow the v{version} format (e.g., v5.0.1).

Website Deployment

The documentation website deploys to Netlify:

  • Build base: website/
  • Build command: cd ../docs && yarn && cd ../website && yarn build && cp _redirects ./build
  • Publish directory: website/build
  • Skip condition: git diff --quiet HEAD^ HEAD -- ../docs/ . (only rebuilds if docs or website changed)
  • Node version: 20
  • Caching: node_modules/.cache, website/node_modules/.cache, .yarn/.cache

Bundle Size Considerations

Redux is designed to be as small as possible:

  • Zero runtime dependencies
  • sideEffects: false enables tree-shaking
  • Production builds use minified error codes (see errors.json)
  • The compose function inlines — if only createStore is imported, compose, bindActionCreators, etc. are tree-shaken away

Tip

The browser-ready ESM build (dist/redux.browser.mjs) is pre-minified with NODE_ENV=production hardcoded. Use it for direct <script type="module"> inclusion without a build step.

5. Operations

Development Workflow

# Install dependencies
yarn

# Run tests (runtime + type-level)
yarn test

# Run tests in watch mode
yarn test:watch

# Run tests with coverage
yarn test:cov

# Type-check the type-level tests only
yarn type-tests

# Lint
yarn lint

# Format
yarn format

# Build all outputs
yarn build

# Lint examples
yarn examples:lint

Error Code System

Redux uses a Babel plugin (scripts/mangleErrors.mts) that replaces verbose error strings with numeric codes in production builds. The mapping lives in errors.json:

Code Error
0 Passing several store enhancers to createStore()
1 Expected enhancer to be a function
2 Expected root reducer to be a function
3 Cannot call getState() while reducer is executing
4 Expected listener to be a function
5 Cannot call subscribe() while reducer is executing
6 Cannot unsubscribe while reducer is executing
7 Actions must be plain objects
8 Actions may not have undefined type
9 Reducers may not dispatch actions
10 Expected nextReducer to be a function
11 Expected observer to be an object
12 Slice reducer returned undefined during initialization
13 Slice reducer returned undefined when probed
14 Slice reducer returned undefined for action
15 Dispatching while constructing middleware
16 bindActionCreators expected object or function
17 Action type must be a string

In production, users see: Minified Redux error #7; visit https://redux.js.org/Errors?code=7 for the full message

Note

The error code system mirrors React's approach. The errors.json file is the source of truth for the mapping between codes and full messages.

Monitoring and Debugging

Redux is designed for observability through Redux DevTools:

  • Time-travel debugging: replay actions, jump to any previous state
  • Action inspection: view every dispatched action and its payload
  • State diff: see what changed between dispatches
  • Import/export: save and load state snapshots

Integration via the DevTools Extension Compose:

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)))

Tip

Redux Toolkit's configureStore enables DevTools automatically in development. With the core redux package, you must compose the DevTools enhancer manually.

6. Performance

State Reference Equality

combineReducers returns the same state object reference if no slice reducer returns a new value. This enables efficient change detection in UI libraries:

// Inside combineReducers:
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
// ...
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state

Listener Implementation

Listeners use a Map<number, ListenerCallback> with numeric IDs (auto-incrementing counter). This is more efficient than array-based implementations for frequent subscribe/unsubscribe patterns. The listener map is copy-on-write: ensureCanMutateNextListeners() creates a shallow copy only when needed.

Middleware Chain

The middleware chain is composed once at store creation via compose. Each dispatch passes through the chain with zero additional allocation — middleware functions are pre-composed into a single function reference.

7. Security

Action Validation

Redux enforces strict action validation in the dispatch function:

  1. Plain object check: isPlainObject(action) — rejects class instances, arrays, Promises, functions
  2. Type existence check: typeof action.type === 'undefined' → error
  3. Type string check: typeof action.type !== 'string' → error (new in Redux 5.x)
  4. Re-entrancy guard: isDispatching flag prevents dispatch during reducer execution

Serialization Safety

For SSR, Redux state must be serialized to HTML. The recommended pattern escapes < to prevent XSS:

JSON.stringify(preloadedState).replace(/</g, '\\x3c')

Caution

Failing to escape serialized state enables XSS attacks via crafted state values containing </script> tags. Always sanitize when embedding state in HTML.

Supply Chain

  • Zero runtime dependencies (the library depends on nothing at runtime)
  • All devDependencies are well-known packages from the JS ecosystem
  • The sideEffects: false declaration ensures no code runs on import alone

Observable Protocol

The Symbol.observable interop uses a defensive check:

const $$observable = (() =>
  (typeof Symbol === 'function' && Symbol.observable) || '@@observable')()

This falls back to a string key if Symbol.observable is not available, maintaining compatibility without polyfill requirements.

8. Developer Internals

Repository Structure

redux/
├── src/                    # Core library source (TypeScript)
│   ├── index.ts            # Public API barrel export
│   ├── createStore.ts      # Store factory (createStore, legacy_createStore)
│   ├── combineReducers.ts  # Reducer composition
│   ├── applyMiddleware.ts  # Middleware store enhancer
│   ├── bindActionCreators.ts # Action creator dispatch binding
│   ├── compose.ts          # Right-to-left function composition
│   ├── types/              # TypeScript type definitions
│   │   ├── actions.ts      # Action, UnknownAction, AnyAction, ActionCreator
│   │   ├── middleware.ts   # Middleware, MiddlewareAPI
│   │   ├── reducers.ts     # Reducer, ReducersMapObject, derived types
│   │   └── store.ts        # Store, Dispatch, StoreEnhancer, Observable
│   └── utils/              # Internal utilities
│       ├── actionTypes.ts  # @@redux/INIT, REPLACE, PROBE_UNKNOWN_ACTION
│       ├── formatProdErrorMessage.ts  # Minified error codes
│       ├── isAction.ts     # Action type guard
│       ├── isPlainObject.ts # Plain object check
│       ├── kindOf.ts       # Dev-mode type name utility
│       ├── symbol-observable.ts # Symbol.observable polyfill
│       └── warning.ts      # Dev-mode console.error wrapper
├── test/                   # Test suite
│   ├── *.spec.ts           # Runtime tests (one per core module)
│   ├── helpers/            # Test utilities (mock reducers, actions, middleware)
│   ├── utils/              # Tests for utility functions
│   └── typescript/         # Type-level tests (*.test-d.ts)
├── examples/               # 10 example applications
│   ├── async/              # Async data fetching with redux-thunk
│   ├── counter/            # React + RTK counter (JS)
│   ├── counter-ts/         # React + RTK counter (TypeScript)
│   ├── counter-vanilla/    # Vanilla JS counter (no React)
│   ├── real-world/         # GitHub API browser with pagination
│   ├── shopping-cart/      # Shopping cart with optimistic updates
│   ├── todomvc/            # TodoMVC with reselect
│   ├── todos/              # Basic todo list
│   ├── todos-with-undo/    # Todos with redux-undo
│   ├── tree-view/          # Dynamic tree with normalized state
│   └── universal/          # Server-side rendering (Express + React)
├── website/                # Docusaurus documentation site
├── docs/                   # Markdown documentation source
├── scripts/                # Build scripts
│   └── mangleErrors.mts    # Babel plugin for error code extraction
├── errors.json             # Error code → message mapping
├── tsup.config.ts          # Build configuration (4 output formats)
├── tsconfig.base.json      # Shared TypeScript settings
├── tsconfig.build.json     # Build-specific TypeScript config
├── tsconfig.test.json      # Type-test TypeScript config
├── vitest.config.mts       # Test runner configuration
├── package.json            # Package metadata (v5.0.1)
└── netlify.toml            # Website deployment config

Module Reference

createStore.ts — Store Factory

The central module. Creates the store object with getState, dispatch, subscribe, replaceReducer, and [Symbol.observable] methods.

Internal state:

Variable Type Purpose
currentReducer Reducer The active root reducer
currentState S | PreloadedState | undefined The current state tree
currentListeners Map<number, ListenerCallback> | null Active listener map (set to null after unsubscribe to allow GC)
nextListeners Map<number, ListenerCallback> Next listener map (copy-on-write)
listenerIdCounter number Auto-incrementing listener ID
isDispatching boolean Re-entrancy guard

Key behaviors:

  • On creation, dispatches { type: '@@redux/INIT{random}' } to populate initial state
  • replaceReducer dispatches { type: '@@redux/REPLACE{random}' } to re-initialize
  • Random suffixes on internal action types prevent reducers from matching them
  • dispatch returns the dispatched action object (middleware may return something else)
  • subscribe returns an unsubscribe function; calling it twice is a no-op
  • The @@observable method returns a minimal observable that calls observer.next(getState()) on every state change

createStore vs legacy_createStore: Both are exported. createStore carries a @deprecated JSDoc annotation; legacy_createStore is identical in behavior but without the deprecation marker. legacy_createStore delegates to createStore internally.

combineReducers.ts — Reducer Composition

Merges an object of slice reducers into a single root reducer.

Validation (at construction time):

  1. Filters out non-function values from the input object
  2. Warns (dev only) if any key maps to undefined
  3. Calls assertReducerShape — invokes each reducer with:
    • undefined state + @@redux/INIT action → must not return undefined
    • undefined state + @@redux/PROBE_UNKNOWN_ACTION{random} → must not return undefined

Runtime (on each dispatch):

  1. Iterates finalReducerKeys, calls each slice reducer with its slice of state + action
  2. Checks if any slice returned a new value (reference inequality)
  3. Also checks if the number of state keys changed
  4. Returns new state object only if something changed; otherwise returns the same reference

Dev-mode warnings:

  • Unexpected keys in state that don't match any reducer
  • Unexpected state shape (not a plain object)

applyMiddleware.ts — Middleware Enhancer

Returns a StoreEnhancer that intercepts createStore to wrap dispatch.

Composition:

const middlewareAPI: MiddlewareAPI = {
  getState: store.getState,
  dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

The dispatch reference in middlewareAPI is a closure — it always points to the latest composed dispatch, not the original. This allows middleware to dispatch through the full chain.

Warning

Calling dispatch during middleware construction (before the chain is composed) throws: "Dispatching while constructing your middleware is not allowed." The initial dispatch is a throwing stub that gets replaced once composition completes.

bindActionCreators.ts — Dispatch Binding

Accepts a single action creator or an object of action creators and a dispatch function. Returns wrapped versions that call dispatch(actionCreator(...args)) automatically.

Edge cases:

  • Non-function values in the input object are silently skipped
  • Passing neither a function nor an object throws with a descriptive error including kindOf() type name
  • The bound function preserves this context via .apply(this, args)

compose.ts — Function Composition

Composes functions from right to left. Special cases:

Input Output
0 functions Identity function: (arg) => arg
1 function Returns that function directly
N functions funcs.reduce((a, b) => (...args) => a(b(...args)))

Heavily overloaded in TypeScript (up to 4 functions with full type inference, then rest-args fallback).

src/utils/ — Internal Utilities

Module Description
actionTypes.ts Three internal action types with random suffixes: INIT (fired on store creation), REPLACE (fired on replaceReducer), PROBE_UNKNOWN_ACTION (used by assertReducerShape to verify reducers handle unknown actions)
isPlainObject.ts Walks the prototype chain to check if an object's prototype is Object.prototype or null. Rejects class instances, arrays, Dates, etc.
isAction.ts Combines isPlainObject with a check for 'type' in action and typeof type === 'string'
kindOf.ts In development, returns a human-readable type name (e.g., 'array', 'date', 'promise', 'weakmap'). In production, returns raw typeof. Used only for error messages.
warning.ts Calls console.error(message), then throw new Error(message) inside a try/catch. The throw enables "break on all exceptions" in browser DevTools without actually propagating.
formatProdErrorMessage.ts Returns a message directing users to redux.js.org/Errors?code=N. Used by the mangleErrors Babel plugin to replace verbose error strings in production builds.
symbol-observable.ts Returns Symbol.observable if available, otherwise the string '@@observable'. Used as a method key on the store for observable interop.

Type System Architecture

Redux 5.0.1 exports a comprehensive type system from src/types/:

types/actions.ts

Type Description
Action<T = string> { type: T } — base action interface
UnknownAction { type: string; [extraProps: string]: unknown } — replaces AnyAction
AnyAction Legacy alias for UnknownAction (deprecated)
ActionCreator<A> (...args: any[]) => A
ActionCreatorsMapObject<A> { [key: string]: ActionCreator<A> }

types/store.ts

Type Description
Store<S, A, StateExt> The store interface with getState, dispatch, subscribe, replaceReducer, [Symbol.observable]
Dispatch<A> (action: A) => A
Unsubscribe () => void
StoreEnhancer<Ext, StateExt> (createStore) => (reducer, preloadedState) => Store
StoreCreator The createStore function type
StoreEnhancerStoreCreator Return type of StoreEnhancer
Observable<T> / Observer<T> TC39 Observable interop types
ListenerCallback () => void
UnknownIfNonSpecific<T> Utility type: resolves to unknown if T is {}

types/reducers.ts

Type Description
Reducer<S, A, PreloadedState> (state: S | PreloadedState | undefined, action: A) => S
ReducersMapObject<S, A> { [K in keyof S]: Reducer<S[K], A> }
StateFromReducersMapObject<M> Derives the combined state type from a reducers map
ActionFromReducersMapObject<M> Union of all action types accepted by reducers in the map
PreloadedStateShapeFromReducersMapObject<M> Partial<> of the derived state (for preloaded state)

types/middleware.ts

Type Description
MiddlewareAPI<D, S> { dispatch: D; getState: () => S }
Middleware<Ext, S, D> (api: MiddlewareAPI) => (next: D) => (action: any) => any

Code Patterns and Conventions

Formatting (from .prettierrc.json):

Setting Value
Semicolons false
Quotes Single
Tab width 2
Trailing commas None
Arrow parens Avoid

Error handling pattern: All public functions validate inputs and throw Error with descriptive messages (dev) or coded messages (prod). The kindOf() utility provides type names in error strings.

Copy-on-write listeners: The listener Map is shallow-copied before mutation via ensureCanMutateNextListeners(). This prevents bugs when subscribers add/remove listeners during notification.

Internal action types use random suffixes:

INIT: `@@redux/INIT${randomString()}`
REPLACE: `@@redux/REPLACE${randomString()}`
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`

This ensures no reducer accidentally handles these internal actions. PROBE_UNKNOWN_ACTION is a function (not a string) because it must generate a unique value on each call during assertReducerShape.

Testing Strategy

Runtime tests (test/*.spec.ts):

Test File Coverage
createStore.spec.ts Store creation, dispatch, subscribe, getState, replaceReducer, observable, error paths
combineReducers.spec.ts Reducer combination, shape validation, warning behavior
applyMiddleware.spec.ts Middleware chain composition, dispatch wrapping
bindActionCreators.spec.ts Single and map binding, error cases
compose.spec.ts 0-arg, 1-arg, multi-arg composition

Utility tests (test/utils/*.spec.ts):

Test File Coverage
isAction.spec.ts Plain object + string type validation
isPlainObject.spec.ts Prototype chain checks
warning.spec.ts Console.error behavior
formatProdErrorMessage.spec.ts Error code formatting

Type-level tests (test/typescript/*.test-d.ts):

9 type-test files using Vitest's expectTypeOf assertions:

Test File Coverage
store.test-d.ts Store type inference, enhancer composition
dispatch.test-d.ts Dispatch type narrowing
reducers.test-d.ts Reducer type derivation
middleware.test-d.ts Middleware type chain
compose.test-d.ts Compose overload resolution
enhancers.test-d.ts Store enhancer type composition
actions.test-d.ts Action type constraints
actionCreators.test-d.ts Action creator type inference
injectedDispatch.test-d.ts Enhanced dispatch types from middleware

Test helpers (test/helpers/):

File Contents
actionCreators.ts addTodo, addTodoAsync, addTodoIfEmpty, unknownAction, dispatchInMiddle, getStateInMiddle, subscribeInMiddle, unsubscribeInMiddle, throwError, noop
actionTypes.ts ADD_TODO, DISPATCH_IN_MIDDLE, GET_STATE_IN_MIDDLE, SUBSCRIBE_IN_MIDDLE, UNSUBSCRIBE_IN_MIDDLE, THROW_ERROR, UNKNOWN_ACTION
middleware.ts thunk (minimal thunk middleware for tests)
reducers.ts reducerNoop, reducerA–reducerC (with state shapes), combined reducers

Running tests:

yarn test            # Full suite (runtime + typecheck)
yarn test:watch      # Watch mode
yarn test:cov        # With coverage
yarn type-tests      # Type-level tests only (tsc --noEmit)

Example Applications

The examples/ directory contains 10 example apps demonstrating different Redux patterns:

Example Stack Pattern Demonstrated
counter React + RTK Basic configureStore + createSlice (JS)
counter-ts React + RTK Same as counter, in TypeScript
counter-vanilla Vanilla JS Redux without any framework; raw DOM manipulation
todos React + Redux Basic todo list with combineReducers
todos-with-undo React + Redux + redux-undo Undo/redo history
todomvc React + Redux + reselect Full TodoMVC spec with memoized selectors
async React + Redux + redux-thunk Async data fetching from Reddit API
shopping-cart React + Redux + redux-thunk Shopping cart with mock API and optimistic updates
real-world React + Redux + custom middleware GitHub API with pagination, caching, normalization (normalizr)
tree-view React + Redux Dynamic tree with normalized flat state
universal Express + React + Redux Server-side rendering with state hydration

Note

The counter and counter-ts examples use Redux Toolkit (@reduxjs/toolkit), not the core redux package directly. All other examples use core Redux APIs (createStore, combineReducers, applyMiddleware).

Potential Improvements

Based on source analysis and community issues:

  1. Documentation consolidation — GitHub issue #4789 discusses merging Redux, RTK, and React-Redux docs into a unified site
  2. Outdated tutorial content — Issue #4480 notes tutorials use legacy ReactDOM.render() instead of createRoot(). Issue #4375 reports broken reactions in the Essentials tutorial
  3. Reselect docs drift — Issue #4866 flags outdated Reselect references in the selectors guide
  4. Next.js guidance — Issue #4713 requests per-route state documentation for Next.js
  5. Type improvements — Issue #3455 reports ActionCreator generic losing argument types; issue #4602 reports conflicts between AppThunk and createAsyncThunk.withTypes
  6. Normalizr replacement — Issue #4699 notes normalizr is no longer maintained; the docs still reference it

Contributing

Code style:

yarn format         # Auto-format with Prettier
yarn format:check   # Check formatting without modifying
yarn lint           # ESLint

Pre-publish checks: prepublishOnly runs format check, lint, and full test suite.

Git conventions:

  • Tags: v{version} (e.g., v5.0.1)
  • .git-blame-ignore-revs exists for large formatting commits
  • .editorconfig and .prettierrc.json enforce consistent formatting

License: MIT

9. Integration Points

Observable Protocol

Redux stores implement the TC39 Observable proposal via Symbol.observable (or the @@observable string fallback). This enables interop with:

  • RxJS: from(store).pipe(...)
  • most.js: from(store).map(...)
  • Any library implementing the Observable consumer protocol

The observable emits the full state tree on every dispatch via observer.next(getState()).

Store Enhancer Protocol

Store enhancers wrap createStore to add capabilities. The enhancer signature:

type StoreEnhancer = (createStore: StoreCreator) =>
  (reducer: Reducer, preloadedState?: any) => Store

Examples: applyMiddleware, Redux DevTools Extension, persistence enhancers. Enhancers compose via compose:

const enhancer = compose(
  applyMiddleware(thunk, logger),
  devToolsEnhancer,
  persistEnhancer
)

Middleware Protocol

Middleware functions follow a curried three-layer pattern:

type Middleware = (api: { dispatch, getState }) =>
  (next: Dispatch) =>
  (action: Action) => any
  • Outer function: receives { dispatch, getState } — called once at store creation
  • Middle function: receives next — the next middleware's dispatch (or the store's base dispatch for the last middleware)
  • Inner function: receives action — called on every dispatch

React Integration

Via react-redux, Redux integrates with React through:

  • <Provider store={store}> — context-based store injection
  • useSelector(selector) — subscribe to state slices with reference equality
  • useDispatch() — access the store's dispatch function
  • connect(mapState, mapDispatch) — HOC-based integration (legacy pattern)