# Redux User Guide

## 1. Quick Start

Redux is a predictable state container for JavaScript applications. It centralizes your application's state and logic in a single store, enforces unidirectional data flow through plain-object actions and pure reducer functions, and notifies subscribers when the state tree changes. The result: applications that behave consistently across client, server, and native environments, and are straightforward to test and debug.

**Key features:**

- Single source of truth — one store holds the entire application state
- State is read-only — changes happen only through dispatched actions
- Pure reducer functions compute the next state predictably
- Middleware pipeline for async logic, logging, and side effects
- Time-travel debugging via Redux DevTools
- Framework-agnostic — works with React, Angular, Vue, or vanilla JS
- First-class TypeScript support with comprehensive type exports
- Observable interoperability (TC39 `Symbol.observable`)
- Tiny footprint — under 2KB minified with zero runtime dependencies

> [!IMPORTANT]
> The Redux team recommends **Redux Toolkit** (`@reduxjs/toolkit`) for all new projects. Redux Toolkit wraps the core `redux` package with simplified APIs, built-in best practices, and pre-configured middleware. The core `redux` package documented here is the foundation that Redux Toolkit builds upon.

**Prerequisites:** Node.js 14+ and a package manager (npm, yarn, or pnpm).

**Install and create your first store:**

```bash
npm install redux
```

```javascript
import { legacy_createStore as createStore } from 'redux'

// Define a reducer
function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}

// Create the store
const store = createStore(counterReducer)

// Subscribe to state changes
store.subscribe(() => console.log(store.getState()))

// Dispatch actions
store.dispatch({ type: 'counter/incremented' })
// { value: 1 }
store.dispatch({ type: 'counter/incremented' })
// { value: 2 }
store.dispatch({ type: 'counter/decremented' })
// { value: 1 }
```

> [!TIP]
> If you are starting a new project, skip the core `redux` package and use Redux Toolkit directly: `npm install @reduxjs/toolkit`. It includes everything documented here plus `configureStore`, `createSlice`, `createAsyncThunk`, and more.

**What to explore next:**

- Configuration section below for middleware and enhancer setup
- Common Tasks for real-world patterns like async data fetching and SSR
- Admin Guide for architecture, module reference, and build system internals

## 2. Installation

### System Requirements

| Requirement | Minimum |
|---|---|
| Node.js | 14+ |
| TypeScript (optional) | 4.7+ (5.x recommended) |
| Package Manager | npm, yarn, or pnpm |

### Install Methods

=== "npm"
    ```bash
    npm install redux
    ```

=== "yarn"
    ```bash
    yarn add redux
    ```

=== "pnpm"
    ```bash
    pnpm add redux
    ```

### Package Exports

Redux 5.0.1 ships multiple module formats for broad compatibility:

| Format | Entry Point | Use Case |
|---|---|---|
| ESM | `dist/redux.mjs` | Modern bundlers (Vite, Rollup, esbuild) |
| CJS | `dist/cjs/redux.cjs` | Node.js `require()` |
| Legacy ESM | `dist/redux.legacy-esm.js` | Webpack 4 (`"module"` field) |
| Browser ESM | `dist/redux.browser.mjs` | `<script type="module">` (minified, production) |
| TypeScript | `dist/redux.d.mts` | Type definitions |

The `exports` field in `package.json` routes imports automatically:

```json
{
  "exports": {
    ".": {
      "types": "./dist/redux.d.mts",
      "import": "./dist/redux.mjs",
      "default": "./dist/cjs/redux.cjs"
    }
  }
}
```

> [!WARNING]
> The `createStore` function is marked `@deprecated` in the source. It still works and will not be removed, but your IDE will display a strikethrough. Import `legacy_createStore` instead to suppress the deprecation warning: `import { legacy_createStore as createStore } from 'redux'`

### Verify Installation

```bash
node -e "const redux = require('redux'); console.log(typeof redux.createStore)"
```

```text
function
```

### Companion Packages

| Package | Purpose |
|---|---|
| `@reduxjs/toolkit` | Official, batteries-included Redux development toolset |
| `react-redux` | Official React bindings (`Provider`, `useSelector`, `useDispatch`) |
| `redux-thunk` | Middleware for dispatching functions (included in RTK) |
| `reselect` | Memoized selector library (included in RTK) |

> [!IMPORTANT]
> Redux 5.x requires action `type` properties to be strings. If you are upgrading from Redux 4.x where non-string types were tolerated, you must update your action types to strings.

### Install Troubleshooting

#### Error: Cannot find module 'redux'

**Cause:** Package not installed or `node_modules` corrupted.

**Solution:**
1. Run `npm install redux`
2. If already installed, delete `node_modules` and lock file, then reinstall

#### TypeScript cannot find type declarations

**Cause:** TypeScript version below 4.7 or `moduleResolution` misconfigured.

**Solution:**
1. Ensure TypeScript 4.7+ is installed
2. Set `"moduleResolution": "node16"` or `"bundler"` in `tsconfig.json`

#### Webpack 4: Cannot use import statement

**Cause:** Webpack 4 does not resolve the `exports` field.

**Solution:** Redux ships a `"module"` field pointing to `dist/redux.legacy-esm.js` for Webpack 4 compatibility. Verify your Webpack config resolves the `module` field.

#### `createStore` shows strikethrough in IDE

**Cause:** Intentional `@deprecated` annotation to encourage Redux Toolkit adoption.

**Solution:**
```javascript
import { legacy_createStore as createStore } from 'redux'
```

## 3. Configuration

Redux has no configuration files. The store is configured entirely through code: the root reducer, optional preloaded state, and optional store enhancers.

### Basic Store Creation

```javascript
import { legacy_createStore as createStore } from 'redux'

const store = createStore(
  rootReducer,      // Required: root reducer function
  preloadedState,   // Optional: initial state for hydration / SSR
  enhancer          // Optional: store enhancer (e.g., applyMiddleware)
)
```

### Adding Middleware

Middleware extends `dispatch` to handle side effects, logging, or async logic. Use `applyMiddleware()` as the store enhancer:

```javascript
import { legacy_createStore as createStore, applyMiddleware } from 'redux'
import { thunk } from 'redux-thunk'

const store = createStore(rootReducer, applyMiddleware(thunk))
```

> [!NOTE]
> Because middleware is potentially asynchronous, `applyMiddleware` should be the first (outermost) enhancer in the composition chain.

### Combining Multiple Enhancers

Use `compose` to merge multiple store enhancers:

```javascript
import { legacy_createStore as createStore, applyMiddleware, compose } from 'redux'
import { thunk } from 'redux-thunk'

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

const store = createStore(
  rootReducer,
  composeEnhancers(applyMiddleware(thunk))
)
```

### Preloaded State (Server-Side Rendering)

Pass server-rendered state as the second argument:

```javascript
// Server: serialize state into HTML
const preloadedState = store.getState()
const html = `<script>window.__PRELOADED_STATE__ = ${
  JSON.stringify(preloadedState).replace(/</g, '\\x3c')
}</script>`

// Client: hydrate
const store = createStore(rootReducer, window.__PRELOADED_STATE__)
delete window.__PRELOADED_STATE__
```

> [!WARNING]
> Always sanitize serialized state with `.replace(/</g, '\\x3c')` to prevent XSS when embedding in HTML.

### Environment Variables

Redux checks `process.env.NODE_ENV` internally:

| Value | Effect |
|---|---|
| `production` | Disables runtime warnings, type-checking assertions, and detailed error messages. Enables minified error codes with links to `redux.js.org/Errors?code=N`. |
| `development` (default) | Enables warnings for common mistakes: unexpected state keys, `undefined` reducer returns, invalid action shapes. |

See the Admin Guide for full build system and module format details.

## 4. Core Usage

### Core Concepts

Redux is built on three principles:

1. **Single source of truth** — the entire application state lives in one store
2. **State is read-only** — the only way to change state is to dispatch an action
3. **Changes are made with pure functions** — reducers take previous state and an action, return new state

### Actions

Actions are plain objects with a string `type` property:

```javascript
const incrementAction = { type: 'counter/incremented' }
const addTodoAction = { type: 'todos/added', payload: { text: 'Learn Redux' } }
```

> [!IMPORTANT]
> Actions **must** be plain objects with a string `type` property. Dispatching non-plain objects, actions with `undefined` type, or actions with non-string type throws an error. To dispatch functions, Promises, or other values, add the appropriate middleware.

**Action creators** are functions that return action objects:

```javascript
const increment = () => ({ type: 'counter/incremented' })
const addTodo = (text) => ({ type: 'todos/added', payload: { text } })

store.dispatch(increment())
store.dispatch(addTodo('Learn Redux'))
```

### Reducers

Reducers are pure functions: `(state, action) => newState`

```javascript
function todosReducer(state = [], action) {
  switch (action.type) {
    case 'todos/added':
      return [...state, { text: action.payload.text, completed: false }]
    case 'todos/toggled':
      return state.map(todo =>
        todo.id === action.payload.id
          ? { ...todo, completed: !todo.completed }
          : todo
      )
    default:
      return state
  }
}
```

**Reducer rules:**

- Must return the current state for any unrecognized action
- Must return initial state when called with `undefined` state
- Must never mutate the existing state — always return a new object
- Initial state may not be `undefined` (use `null` for intentionally empty)
- Must not call `store.dispatch()`, `store.getState()`, or `store.subscribe()`

### Combining Reducers

Use `combineReducers` to split logic by state domain:

```javascript
import { combineReducers } from 'redux'

const rootReducer = combineReducers({
  todos: todosReducer,
  visibilityFilter: visibilityFilterReducer
})
```

```text
Resulting state shape:
{
  todos: [...],
  visibilityFilter: 'SHOW_ALL'
}
```

> [!NOTE]
> Each key in the object passed to `combineReducers` becomes a top-level key in the state tree. Each slice reducer receives only its own slice — not the entire state tree.

### The Store

The store holds the state tree and provides methods to interact with it:

| Method | Description |
|---|---|
| `store.getState()` | Returns the current state tree |
| `store.dispatch(action)` | Dispatches an action; returns the action |
| `store.subscribe(listener)` | Registers a listener called after every dispatch; returns an `unsubscribe` function |
| `store.replaceReducer(nextReducer)` | Replaces the root reducer (for code splitting / hot reload) |
| `store[Symbol.observable]()` | Returns a minimal observable for reactive library interop |

### Subscribing to State Changes

```javascript
const unsubscribe = store.subscribe(() => {
  console.log('State changed:', store.getState())
})

// Stop listening
unsubscribe()
```

> [!WARNING]
> Do not call `store.getState()`, `store.subscribe()`, or `store.dispatch()` from inside a reducer. The reducer is already executing within a dispatch call, and these operations will throw.

### Binding Action Creators

`bindActionCreators` wraps action creators so they dispatch automatically:

```javascript
import { bindActionCreators } from 'redux'

const actions = bindActionCreators(
  { increment, decrement, addTodo },
  store.dispatch
)

actions.increment()           // dispatched automatically
actions.addTodo('Learn Redux') // dispatched automatically
```

This is useful when passing action creators to components that should not know about `dispatch`.

### Middleware

Middleware sits between dispatch and the reducer. It provides extension points for logging, crash reporting, async actions, and more.

Middleware signature:

```javascript
const myMiddleware = store => next => action => {
  console.log('Dispatching:', action)
  const result = next(action)
  console.log('Next state:', store.getState())
  return result
}
```

Apply during store creation:

```javascript
const store = createStore(
  rootReducer,
  applyMiddleware(myMiddleware, thunk, logger)
)
```

### Composing Functions

`compose` chains single-argument functions from right to left:

```javascript
import { compose } from 'redux'

const enhance = compose(f, g, h)
// equivalent to: (...args) => f(g(h(...args)))
```

Primarily used to compose multiple store enhancers.

### Utility Functions

| Function | Description |
|---|---|
| `isAction(value)` | Returns `true` if value is a plain object with a string `type` property |
| `isPlainObject(value)` | Returns `true` if value is a plain object (prototype is `Object.prototype` or `null`) |

```javascript
import { isAction, isPlainObject } from 'redux'

isAction({ type: 'INCREMENT' })  // true
isAction({ type: 123 })          // false
isAction('INCREMENT')            // false

isPlainObject({})                // true
isPlainObject(new Date())        // false
```

### TypeScript Usage

Redux 5.0.1 is written in TypeScript and ships comprehensive type definitions:

```typescript
import type {
  Store, Dispatch, Action, UnknownAction, AnyAction,
  Reducer, ReducersMapObject, Middleware, MiddlewareAPI,
  StoreEnhancer, StoreCreator, StoreEnhancerStoreCreator,
  ActionCreator, ActionCreatorsMapObject,
  Unsubscribe, Observable, Observer,
  StateFromReducersMapObject, ReducerFromReducersMapObject,
  ActionFromReducer, ActionFromReducersMapObject,
  PreloadedStateShapeFromReducersMapObject
} from 'redux'
```

**Typing a reducer:**

```typescript
interface CounterState {
  value: number
}

type CounterAction =
  | { type: 'counter/incremented' }
  | { type: 'counter/decremented' }

const counterReducer: Reducer<CounterState, CounterAction> = (
  state = { value: 0 },
  action
) => {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}
```

**Typing middleware:**

```typescript
import type { Middleware } from 'redux'

const loggerMiddleware: Middleware = store => next => action => {
  console.log('Dispatching:', action)
  const result = next(action)
  console.log('Next state:', store.getState())
  return result
}
```

> [!TIP]
> Redux Toolkit's `configureStore` infers `dispatch` and `getState` types automatically. If using the core `redux` package directly, you must type the store, dispatch, and state manually.

## 5. Common Tasks

### Task: Counter with Increment/Decrement

**Goal:** Create a simple counter store.

```javascript
import { legacy_createStore as createStore } from 'redux'

function counterReducer(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

const store = createStore(counterReducer)
store.subscribe(() => console.log(store.getState()))

store.dispatch({ type: 'INCREMENT' })  // 1
store.dispatch({ type: 'INCREMENT' })  // 2
store.dispatch({ type: 'DECREMENT' })  // 1
```

### Task: Todo List with Visibility Filter

**Goal:** Manage a list of todos with combined reducers.

```javascript
import { legacy_createStore as createStore, combineReducers } from 'redux'

function todosReducer(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: action.id, text: action.text, completed: false }]
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      )
    default:
      return state
  }
}

function visibilityFilterReducer(state = 'SHOW_ALL', action) {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

const store = createStore(combineReducers({
  todos: todosReducer,
  visibilityFilter: visibilityFilterReducer
}))
```

### Task: Async Data Fetching with Thunk Middleware

**Goal:** Fetch remote data and store results.

**Prerequisites:** `npm install redux-thunk`

```javascript
import { legacy_createStore as createStore, applyMiddleware } from 'redux'
import { thunk } from 'redux-thunk'

function postsReducer(state = { items: [], isFetching: false }, action) {
  switch (action.type) {
    case 'FETCH_POSTS_REQUEST':
      return { ...state, isFetching: true }
    case 'FETCH_POSTS_SUCCESS':
      return { ...state, isFetching: false, items: action.posts }
    case 'FETCH_POSTS_FAILURE':
      return { ...state, isFetching: false, error: action.error }
    default:
      return state
  }
}

const store = createStore(postsReducer, applyMiddleware(thunk))

function fetchPosts() {
  return async (dispatch) => {
    dispatch({ type: 'FETCH_POSTS_REQUEST' })
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts')
      const posts = await response.json()
      dispatch({ type: 'FETCH_POSTS_SUCCESS', posts })
    } catch (error) {
      dispatch({ type: 'FETCH_POSTS_FAILURE', error: error.message })
    }
  }
}

store.dispatch(fetchPosts())
```

### Task: Logging Middleware

**Goal:** Log every action and resulting state.

```javascript
const logger = store => next => action => {
  console.group(action.type)
  console.log('Previous state:', store.getState())
  console.log('Action:', action)
  const result = next(action)
  console.log('Next state:', store.getState())
  console.groupEnd()
  return result
}

const store = createStore(rootReducer, applyMiddleware(logger))
```

### Task: Server-Side Rendering (Universal App)

**Goal:** Render state on the server and hydrate on the client.

**Server:**

```javascript
import { legacy_createStore as createStore } from 'redux'
import { renderToString } from 'react-dom/server'
import { Provider } from 'react-redux'

function handleRequest(req, res) {
  const store = createStore(rootReducer, { counter: 42 })
  const html = renderToString(
    <Provider store={store}><App /></Provider>
  )
  const finalState = store.getState()
  res.send(`
    <div id="app">${html}</div>
    <script>
      window.__PRELOADED_STATE__ = ${JSON.stringify(finalState).replace(/</g, '\\x3c')}
    </script>
    <script src="/bundle.js"></script>
  `)
}
```

**Client:**

```javascript
const store = createStore(rootReducer, window.__PRELOADED_STATE__)
delete window.__PRELOADED_STATE__
```

### Task: Code Splitting with replaceReducer

**Goal:** Dynamically inject reducers for lazy-loaded routes.

```javascript
import { combineReducers } from 'redux'

let currentReducers = { app: appReducer }
const store = createStore(combineReducers(currentReducers))

function injectReducer(key, reducer) {
  currentReducers[key] = reducer
  store.replaceReducer(combineReducers(currentReducers))
}

// Dynamically add on route load:
import('/routes/dashboard').then(module => {
  injectReducer('dashboard', module.dashboardReducer)
})
```

### Task: Custom API Middleware

**Goal:** Intercept API call actions (pattern from the `real-world` example).

```javascript
const CALL_API = Symbol('CALL_API')

const apiMiddleware = store => next => action => {
  const callAPI = action[CALL_API]
  if (typeof callAPI === 'undefined') {
    return next(action)
  }

  const { endpoint, types } = callAPI
  const [requestType, successType, failureType] = types

  next({ type: requestType })

  return fetch('https://api.github.com/' + endpoint)
    .then(response => response.json())
    .then(response => next({ type: successType, response }))
    .catch(error => next({ type: failureType, error: error.message }))
}
```

### Task: Undo/Redo

**Goal:** Add undo/redo using `redux-undo`.

**Prerequisites:** `npm install redux-undo`

```javascript
import { legacy_createStore as createStore } from 'redux'
import undoable from 'redux-undo'

const store = createStore(undoable(todosReducer))

// State shape: { past: [...], present: currentState, future: [...] }
store.dispatch({ type: 'ADD_TODO', text: 'Learn Redux' })
store.dispatch({ type: '@@redux-undo/UNDO' })
store.dispatch({ type: '@@redux-undo/REDO' })

// Access current state:
store.getState().present
```

### Task: Observable / RxJS Interop

**Goal:** Use Redux with RxJS.

```javascript
import { from } from 'rxjs'
import { map } from 'rxjs/operators'

const state$ = from(store)
state$.pipe(
  map(state => state.counter)
).subscribe(counter => console.log('Counter:', counter))
```

> [!NOTE]
> Redux implements the TC39 Observable proposal (`Symbol.observable`). Any library supporting this protocol (RxJS, most.js) can subscribe to the store directly.

### Task: Using with React

**Prerequisites:** `npm install react-redux`

```javascript
import React from 'react'
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'
import { legacy_createStore as createStore } from 'redux'

const store = createStore(rootReducer)

createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <App />
  </Provider>
)
```

Then use `useSelector` and `useDispatch` hooks from `react-redux` in components.

### Task: Conditional Data Fetching (Skip if Cached)

**Goal:** Only fetch data if it is not already in the store.

```javascript
const fetchPostsIfNeeded = (subreddit) => (dispatch, getState) => {
  const posts = getState().postsBySubreddit[subreddit]
  if (posts && !posts.didInvalidate) {
    return  // already cached
  }
  dispatch(fetchPosts(subreddit))
}

store.dispatch(fetchPostsIfNeeded('reactjs'))
```

## 6. Troubleshooting

### Diagnostic Decision Tree

```text
Store not working?
├── Error on dispatch? → Check action is plain object with string type
├── State not updating? → Check reducer returns new object (not mutating)
├── Subscribers not firing? → Verify you are dispatching, not just calling action creator
├── Middleware not applied? → Verify applyMiddleware() passed as enhancer
└── TypeScript errors? → Check type imports and reducer annotations
```

### Error: `Actions must be plain objects`

**Cause:** You dispatched a function, Promise, class instance, or other non-plain object. Common when using thunks without `redux-thunk` middleware.

**Solution:**
1. If dispatching functions, install and apply `redux-thunk`:
   ```javascript
   import { thunk } from 'redux-thunk'
   const store = createStore(reducer, applyMiddleware(thunk))
   ```
2. If dispatching a regular action, ensure it is a plain object: `{ type: 'MY_ACTION' }`

**Prevention:** Always apply `redux-thunk` if any action creators return functions.

### Error: `Actions may not have an undefined "type" property`

**Cause:** Dispatched an object without a `type` property, or `type` is `undefined`. Often a misspelled constant.

**Solution:** Verify action creator returns an object with `type` set to a defined string constant.

**Prevention:** Use string literals directly or verified constant imports.

### Error: `Action "type" property must be a string`

**Cause:** Action `type` is a number, Symbol, or other non-string value. Redux 5.x enforces string types.

**Solution:** Convert action types to strings.

### Error: `Reducers may not dispatch actions`

**Cause:** A reducer called `store.dispatch()` during execution.

**Solution:** Move dispatch calls to middleware, thunks, or subscriber callbacks.

**Prevention:** Keep reducers pure: `(state, action) => newState` with no side effects.

### Error: `The slice reducer for key "X" returned undefined during initialization`

**Cause:** A reducer returned `undefined` when called with `undefined` state.

**Solution:** Add a default parameter:
```javascript
function myReducer(state = initialState, action) { ... }
```

**Prevention:** Always use default parameters. Use `null` for intentionally empty state, never `undefined`.

### Error: `The slice reducer for key "X" returned undefined when probed with a random type`

**Cause:** Reducer lacks a `default` case returning current state.

**Solution:**
```javascript
default:
  return state
```

### Error: `Dispatching while constructing your middleware is not allowed`

**Cause:** Middleware called `dispatch()` during initial setup, before the chain is composed.

**Solution:** Only call `dispatch` inside the innermost function (the one receiving `action`).

### Error: `It looks like you are passing several store enhancers to createStore()`

**Cause:** Multiple enhancers passed as separate arguments.

**Solution:**
```javascript
import { compose, applyMiddleware } from 'redux'
const store = createStore(reducer, compose(applyMiddleware(thunk), devToolsEnhancer))
```

### Error: `You may not call store.getState() while the reducer is executing`

**Cause:** Code inside a reducer called `store.getState()`.

**Solution:** The reducer already receives state as its first argument. Use that directly.

### Error: `You may not call store.subscribe() while the reducer is executing`

**Cause:** Subscription attempt inside a reducer.

**Solution:** Move subscriptions outside reducers.

> [!CAUTION]
> All errors referencing "while the reducer is executing" indicate code running inside a dispatch cycle. Reducers must be pure — receive state and action, return new state, do nothing else.

### State not updating after dispatch

**Cause:** Reducer mutates existing state instead of returning a new object.

**Solution:**
```javascript
// Wrong — mutation:
state.items.push(newItem)
return state

// Correct — immutable update:
return { ...state, items: [...state.items, newItem] }
```

### Subscribers called but UI not updating

**Cause:** With React-Redux, components re-render based on reference equality. If the reducer returns the same object, no re-render occurs.

**Solution:** Ensure reducers return new objects when state changes. `combineReducers` handles this — if any slice reducer returns a new value, the root object is new.

### GitHub Issue #4375: Reactions update broken in tutorial

The Essentials tutorial's reactions feature does not update correctly in `<SinglePostPage />`. This is a documentation issue, not a core Redux bug.

### GitHub Issue #3455: TypeScript `ActionCreator` loses argument types

The `ActionCreator` generic type may not preserve argument types in all cases. Use explicit function types or Redux Toolkit's `createAction` for better inference.

> [!WARNING]
> GitHub issue #4602 reports a TypeScript conflict between custom `AppThunk` types and `createAsyncThunk.withTypes`. This affects Redux Toolkit usage — see RTK documentation for workarounds.

## 7. FAQ

**Q: Should I use Redux or Redux Toolkit?**

Use Redux Toolkit for all new projects. It includes the core `redux` package and adds `configureStore`, `createSlice`, `createAsyncThunk`, and other utilities that eliminate boilerplate. The core `redux` package is for learning purposes or building custom abstractions.

**Q: Can I use Redux without React?**

Yes. Redux is framework-agnostic with zero runtime dependencies. Use it with Angular, Vue, Svelte, vanilla JavaScript, or Node.js. The `store.subscribe()` API works in any JavaScript environment.

**Q: Is `createStore` being removed?**

No. `createStore` will remain indefinitely. The `@deprecated` annotation is a visual IDE hint to encourage Redux Toolkit. Use `legacy_createStore` to import without the warning.

**Q: Do I need middleware for async operations?**

Yes. Redux's base `dispatch` only accepts plain objects. For async operations, use middleware:

| Approach | Library | Pattern |
|---|---|---|
| Thunks | `redux-thunk` | Dispatch functions receiving `(dispatch, getState)` |
| Sagas | `redux-saga` | Generator functions yielding effects |
| Observables | `redux-observable` | RxJS epics transforming action streams |
| Listeners | `@reduxjs/toolkit` | `createListenerMiddleware` for reactive logic |

**Q: Can I have multiple stores?**

Technically yes, but strongly discouraged. A single store enables DevTools, simplifies SSR, and makes state management predictable. Use `combineReducers` or RTK's `combineSlices` for logical separation within one store.

**Q: How do I reset the entire store?**

Handle a reset action in your root reducer:

```javascript
const rootReducer = (state, action) => {
  if (action.type === 'RESET') state = undefined
  return appReducer(state, action)
}
```

**Q: What is `__DO_NOT_USE__ActionTypes`?**

An internal export containing Redux's private action types (`@@redux/INIT`, `@@redux/REPLACE`, `@@redux/PROBE_UNKNOWN_ACTION`). Do not reference these in application code — they are implementation details.

**Q: How does `sideEffects: false` in package.json help?**

It tells bundlers that Redux has no module-level side effects, enabling tree-shaking. Unused exports are removed from production bundles. This only works with ESM imports — CJS `require()` cannot be tree-shaken.

**Q: Why does `combineReducers` warn about unexpected keys?**

In development, if the state object has keys that don't match any reducer, `combineReducers` logs a warning. This catches typos and state shape mismatches. The warning is suppressed in production.

**Q: Can I dispatch from inside a subscriber?**

Yes, but with caveats. Subscriptions are snapshotted before each dispatch. If you subscribe/unsubscribe during notification, the change takes effect on the *next* dispatch. Nested dispatches from subscribers are allowed but can make state flow harder to follow.

---

_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. Unofficial; not affiliated with or reviewed by the project._
