skip to content

Why does Redux keep global state in one store of plain serializable data, and what breaks if you store a Promise or class instance?

level: middleimportance: should knowfreq 50%

answer

  1. one tree, one place to look
  2. JSON in, JSON out
  3. hydrate, persist, replay
  4. core stores whatever you return

basics

~20 s

One store holding plain, serializable data lets you inspect, persist, hydrate from the server and replay the whole state; a Promise, Map or class instance breaks serialization and debugging tools, and objects mutated in place can stop the UI updating.

solid answer

~40 s

Redux's first principle is a **single source of truth**: the global state lives in one object tree in one store. Because that tree is plain data, you can serialize it to send from server to client, persist and restore it, show it in the DevTools and replay an action log against it. The style guide therefore says not to put Promises, Symbols, Maps, Sets, functions or class instances in state or actions. The core does not enforce this: `createStore` saves whatever the reducer returns. Things just break quietly: `JSON.stringify` turns a Map into `{}`, a Promise's result is invisible, and a class instance updated through its own methods mutates in place, so reference checks miss the change. The one exception is actions that middleware such as redux-thunk intercepts before they reach reducers.

go deeper

for a junior

State the single-source-of-truth principle and list what belongs in the store: plain objects, arrays and primitives, with no functions or class instances.

for a middle

Explain what serializability enables, such as hydration, persistence, inspection and replay, and why an in-place mutating class instance defeats reference checks.

for a senior

Know where enforcement actually lives: the core validates actions but not state, and Toolkit's development middleware warns about non-serializable values and mutations.

for a principal

Discuss the boundary of the global store: what is worth centralizing, what stays local, and how live resources such as sockets are referenced by ID instead.

## The principle Redux describes itself with three principles. The first is **single source of truth**: *the global state of your application is stored in an object tree within a single store*. The style guide adds a rule to go with it: **only one Redux store per app**, created in one place and handed to the UI through a provider, not imported all over the code. The second half of the design is what the tree contains: **plain, serializable data**, meaning objects, arrays, strings, numbers, booleans and `null`. ## What one tree buys you - **Server rendering and hydration.** The server can build state, serialize it into the page and hand it to the client store as preloaded state with no custom code. - **Persistence.** Saving and restoring all or part of the tree is `JSON.stringify` and `JSON.parse`. - **Debugging.** One tree can be inspected, diffed between actions and exported as a snapshot. - **Replay.** Because every change is an action applied by pure reducers to plain data, recorded actions can be replayed to reproduce a bug. The DevTools' time travel is built on this. - **Undo/redo.** Keeping past versions of one tree is simple when the tree is immutable data. ## What breaks with non-serializable values | Value in state | What goes wrong | |---|---| | `Promise` | No serializable form; its status is invisible to tools; hydration cannot recreate it | | `Map` / `Set` | `JSON.stringify` turns them into `{}`, so persistence and hydration lose the data | | Class instance | Methods and prototype are lost on serialization; methods that mutate `this` change state in place | | Function | Dropped by JSON; cannot be inspected or replayed | | `Date` | Becomes a string on the way through JSON, so the type changes after a round trip | The in-place mutation point deserves emphasis. A class instance with an `addItem()` method that pushes into its own array changes the state **without producing a new reference**. UI bindings that compare references, as react-redux does, see "no change" and skip the render, and any snapshot tool now shows the mutated value in past states too. ## What the core does and does not enforce 1. **Actions**: the core `dispatch` throws unless the action is a plain object whose `type` is a string (Redux 5). 2. **State**: the core does **not** check state at all. `createStore` stores whatever the reducer returns. 3. **Development checks**: Redux Toolkit's `configureStore` adds development-only middleware that warns about non-serializable values and detects mutations. That is a Toolkit feature, not core behaviour. ## The documented exception Non-serializable values are acceptable **in actions** when a middleware intercepts and stops the action before it reaches reducers. A function dispatched to redux-thunk is the standard example: it never becomes state and never appears in the reducer's action log. ## One store, not several It is technically possible to call `createStore` twice, but the style guide lists **one store per app** among its essential rules, for concrete reasons: - **Actions cannot cross stores.** An action dispatched to store A never reaches store B's reducers, so one event that should update both needs two dispatches and can leave them inconsistent in between. - **The provider serves one store.** A React subtree gets its store from the nearest provider, so components that need data from two stores need awkward nesting or custom context. - **Tooling assumes one tree.** Snapshots, persistence and replay work on one state tree; several trees multiply the setup and cannot be replayed as one sequence. What scales instead is **one store with many slices**: each feature owns a reducer, and the root reducer composes them. The style guide also advises that application code should not import the store directly. It reaches components through the provider and other logic through middleware, which keeps tests and server rendering free to create their own store instances. ## What to store instead - Store **IDs and plain records**, not live objects such as sockets, class models or DOM nodes; keep those outside the store and look them up by ID. - Store **timestamps as numbers or ISO strings**, not `Date` objects. - Store **request status as data** (`'idle' | 'loading' | 'succeeded' | 'failed'`), not the Promise. - Keep **derived values** out of state and compute them when you read.

  • Does one store mean every piece of state in the app belongs in Redux?
    No. The single store holds global state: data many parts of the app share or that must survive navigation. The Redux style guide explicitly says to evaluate where each piece of state should live; form drafts, open or closed toggles and hover state usually stay local to their components.
  • Why is a Date object in state a problem when it looks harmless?
    It is not plain data: it passes through JSON as a string, so after persistence or server hydration the field's type changes, and code that calls date methods on it breaks. Storing a timestamp number or an ISO string keeps the type identical on both sides.

saying these in an interview costs you the question

  • Redux core throws when a reducer puts a Map into state
  • Several stores per feature are the recommended way to scale Redux
  • Class instances with methods are fine because they serialize with JSON
  • Putting the loading Promise in state is how you track requests
  • A Date object survives a JSON round trip unchanged