skip to content

What does Redux Toolkit's configureStore set up by default that a bare Redux 5 createStore call leaves out?

level: middleimportance: must knowfreq 62%

answer

  1. one options object, sensible defaults
  2. reducer map becomes combineReducers
  3. thunk always, checks only in development
  4. middleware option is a callback

basics

~10 s

configureStore combines a reducer map automatically, installs the thunk middleware, adds development-only checks for state mutation, non-serializable values and dispatched action creators, and connects the Redux DevTools extension, all without hand-written enhancer composition.

solid answer

~40 s

`configureStore({ reducer })` accepts either a root reducer or an object of slice reducers, which it passes to `combineReducers` for you. If you do not pass `middleware`, it installs `getDefaultMiddleware()`: the **thunk** middleware in every build, plus three development-only checks — an **immutability** check that throws when state is mutated, a **serializability** check that logs an error when an action or the state holds a non-serializable value, and an **action-creator** check that warns when you dispatch the creator instead of calling it. It also wires the **Redux DevTools** extension through `devTools`, which defaults to `true`, with action stack traces captured in development. In RTK 2 the `middleware` option must be a callback, `(getDefaultMiddleware) => getDefaultMiddleware().concat(logger)`, so you add to the defaults rather than silently replacing them.

code

ts · 15 lines
ts
import { configureStore } from '@reduxjs/toolkit'
import todosReducer from './todosSlice'
import filtersReducer from './filtersSlice'
import { logger } from './logger'
import { api } from './apiClient'

export const store = configureStore({
  reducer: { todos: todosReducer, filters: filtersReducer },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({ thunk: { extraArgument: { api } } }).concat(logger),
  devTools: process.env.NODE_ENV !== 'production',
})

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

go deeper

for a junior

Recall that configureStore takes a reducer map, adds thunk automatically, and connects Redux DevTools without extra setup code.

for a middle

Explain the three development-only checks and how each reports a problem, and show the RTK 2 middleware callback that concatenates onto getDefaultMiddleware.

for a senior

Decide how to handle a non-serializable warning by fixing the data first and scoping any ignore narrowly, and make a deliberate choice about DevTools exposure in production.

for a principal

Treat the default checks as guard rails that encode Redux's contract; relaxing them globally trades early detection for later debugging cost across the whole team.

## The problem configureStore solves With plain Redux 5, a production-ready store means importing `createStore` (marked deprecated in Redux 5, with `legacy_createStore` as the same function under an undeprecated name), calling `combineReducers`, calling `applyMiddleware` with a thunk middleware, and composing that with the DevTools extension's enhancer. Every project wrote the same dozen lines and many got the composition order wrong. `configureStore` from `@reduxjs/toolkit` is one call with defaults for all of it. ## What each option defaults to | Option | Default behaviour | |---|---| | `reducer` | a function is used as the root reducer; a plain object of reducers is passed to `combineReducers` | | `middleware` | `getDefaultMiddleware()` when omitted | | `devTools` | `true`: connects the Redux DevTools extension if it is installed | | `preloadedState` | none; pass it to hydrate the store | | `enhancers` | `getDefaultEnhancers()`, which includes the middleware enhancer | ## What getDefaultMiddleware installs 1. **thunk** — always, in every environment, so `dispatch(fn)` works out of the box. Options: `thunk: { extraArgument }` injects a dependency such as an API client. 2. **Immutability check** — development only. It tracks the state and **throws** when it finds a mutation, either one made inside a dispatch or one made between dispatches, such as a component editing a selected object, which is caught on the next dispatch. 3. **Serializability check** — development only. It walks each dispatched action and the resulting state and **logs** `A non-serializable value was detected...` with the path when it finds a `Promise`, a class instance, a `Map` or a function. It warns rather than throwing. 4. **Action-creator check** — development only. It warns when you write `dispatch(todoAdded)` instead of `dispatch(todoAdded())`. Each check can be switched off or tuned: `getDefaultMiddleware({ serializableCheck: { ignoredActions: [...], ignoredPaths: [...] } })`. By default the serializability check already skips `meta.arg` on actions, where `createAsyncThunk` stores its argument. In a production build, `process.env.NODE_ENV === 'production'` removes the three checks entirely, so they cost nothing there. ## Why those particular checks - Redux depends on **immutable updates**: React-Redux and the DevTools find changes by comparing references, so a mutation can leave the UI stale. The immutability check catches it at the moment it happens. - Redux state should be **plain serialisable data** so that it can be persisted, sent across the wire for server rendering, and replayed or exported by the DevTools. The serializability check keeps a stray `Date` object or response object from creeping in. ## RTK 2 details that trip people up - The `middleware` option **must be a callback** in RTK 2. Passing an array throws `` `middleware` field must be a callback `` in development. - Returning a new array from the callback **replaces** the defaults. `middleware: () => [logger]` drops thunk and every check; the intended form is `getDefaultMiddleware().concat(logger)`. - The same middleware instance included twice triggers a duplicate-middleware error. ## DevTools and production `devTools: true` is the default in **every** environment; RTK does not switch it off in production. If you do not want a production store to be inspectable through the extension, pass `devTools: process.env.NODE_ENV !== 'production'`. When enabled, `configureStore` turns on the extension's `trace` option in development, so each action in the DevTools log carries the stack that dispatched it. ## A typical migration Converting an existing store is usually mechanical: 1. Replace `createStore(rootReducer, preloadedState, composeEnhancers(applyMiddleware(thunk, logger)))` with `configureStore({ reducer: rootReducer, preloadedState, middleware: (gDM) => gDM().concat(logger) })`. 2. Delete the manual DevTools composition. 3. Run the app in development and fix what the new checks report: commonly a component that mutates an object it selected, or an action that carries a `Date` or an `Error` instance. Hand-written reducers keep working unchanged, so slices can be introduced afterwards one feature at a time. Derive `RootState` and `AppDispatch` from the configured store so that typed hooks reflect the middleware actually installed. ## Summary for an interview - one call replaces `createStore` + `combineReducers` + `applyMiddleware` + DevTools composition; - thunk is always on; three safety checks run in development only; - the immutability check throws, the serializability check logs an error, the action-creator check warns; - in RTK 2, customise middleware with `getDefaultMiddleware().concat(...)` inside a callback.

  • A Redux Toolkit store logs 'A non-serializable value was detected in the state'. What are your options?
    First, prefer fixing the data: store an ISO string or timestamp instead of a `Date`, and keep class instances and promises out of state. If the value genuinely must pass through, for example a file handle in one action, configure `serializableCheck` with `ignoredActions`, `ignoredActionPaths` or `ignoredPaths` for that spot only, rather than disabling the check globally.
  • Does Redux Toolkit's immutability check slow down a production build?
    No. `getDefaultMiddleware` only adds the immutability, serializability and action-creator checks when `process.env.NODE_ENV` is not `'production'`, so production builds carry only the thunk middleware plus whatever you added. The checks can be slow on very large development states, which is what their `warnAfter` option reports.

saying these in an interview costs you the question

  • configureStore's safety checks also run in production builds
  • Passing middleware: () => [logger] adds logger to the defaults
  • The serializability check throws and stops the dispatch
  • configureStore disables the DevTools connection in production automatically
  • You still have to install and apply redux-thunk yourself