How does Redux DevTools time-travel debugging work, and what in your reducers or state stops it from replaying correctly?
answer
- a recorded list of actions
- jump restores, skip recomputes
- reducers are run again
- pure, immutable, serialisable
basics
~20 sRedux DevTools records each dispatched action with the state it produced; jumping restores a recorded state, and skipping an action recomputes later states by re-running reducers. Impure reducers, mutated state and non-serialisable values make that replay wrong or impossible.
solid answer
~50 sWith the Redux DevTools extension connected — `configureStore` does it through its `devTools` option — every action that reaches the reducers is logged with the resulting state, and panels show the action, the state and a diff. **Jumping** to an earlier action swaps the app's current state for the recorded one, and React-Redux re-renders the UI from it. **Skipping** an action recomputes every later state by running the reducers again over the remaining actions, so this only works when reducers are **pure**: `Date.now()`, `Math.random()` or a network call inside a reducer gives different results, or repeats side effects, on replay. **Mutating** state corrupts the recorded history, because earlier snapshots change along with the current one. **Non-serialisable** values such as class instances, `Map`s or functions display poorly and break exporting, importing and persisting a session. Thunks are not replayed; only the plain actions they dispatched are.
code
ts · 16 linesimport { configureStore } from '@reduxjs/toolkit'
import { rootReducer } from './rootReducer'
export const store = configureStore({
reducer: rootReducer,
devTools:
process.env.NODE_ENV === 'production'
? false
: {
name: 'Shop',
maxAge: 100,
actionsDenylist: ['clock/tick'],
trace: true,
traceLimit: 25,
},
})go deeper
Know how to open the action log, inspect an action's payload, the state tree and the diff, and jump back to an earlier state.
Explain jump versus skip, why skip re-runs reducers, and why impure reducers, mutation and non-serialisable values break replay.
Use traces and sanitizers to debug real issues, keep sensitive state out of the recorded history, and make a deliberate production DevTools decision.
Treat replayability as a design constraint on state shape and reducer purity, and decide the team's policy on production inspectability.
## What the DevTools record The Redux DevTools browser extension connects to a store through a store enhancer. Redux Toolkit's `configureStore` adds it automatically through its `devTools` option, which defaults to `true`; with plain Redux you compose the extension's enhancer yourself. Once connected, the extension keeps a **history**: - each action that reaches the reducers, in order; - the state computed after each action; - by default the last 50 actions (the extension's `maxAge` option), with older entries dropped. For each entry you can inspect the **Action** (type and payload), the **State** tree, and the **Diff** between the previous and next state. With `configureStore` in development, the `trace` option is on, so each action also carries the stack trace of the code that dispatched it — the fastest way to answer "who dispatched this?". ## How time travel works Time travel is a consequence of Redux's design: state is the result of applying reducers to a sequence of actions. 1. **Jump.** Selecting an earlier action replaces the store's current state with the state recorded after that action. React-Redux's subscriptions fire and the UI re-renders as it looked at that moment. No reducer runs. 2. **Skip.** Toggling an action off removes it from the sequence. The extension recomputes every later state by running the reducers again over the remaining actions, starting from the state before the skipped one. 3. **Replay and slider.** Moving through the history re-applies recorded states step by step, which makes it possible to watch a bug unfold. 4. **Export and import.** The history can be saved to a file and loaded elsewhere to reproduce a session, which relies on actions and state being serialisable. Only **actions** are in the history. A thunk is a function intercepted by middleware before it reaches the reducers, so its logic and network calls are never replayed; only the plain actions it dispatched appear. ## What breaks replay | Problem in the code | What you see in the DevTools | |---|---| | **Impure reducer** — `Date.now()`, `Math.random()`, reading `localStorage`, calling an API | skipping or replaying an action produces different states than the original run, or repeats side effects | | **Mutated state** — `state.items.push(x)` in a hand-written reducer, or a component editing a selected object | earlier snapshots change along with the current state; diffs show nothing and jumping back shows wrong data | | **Non-serialisable values** — class instances, `Map`, `Set`, `Date`, promises, functions | values display poorly, and export, import or persisted sessions lose or corrupt them | | **Huge state or payloads** | the extension becomes slow; the history consumes memory | Redux Toolkit guards against the first rows in development: Immer-based reducers make mutation-by-mistake rare, the immutability check throws on accidental mutation, and the serializability check logs non-serialisable values. ## Keeping the DevTools useful - Keep reducers **pure**; generate ids and timestamps in action creators or `prepare` callbacks so the values are recorded in the action itself. - Keep state **plain data**: ISO strings instead of `Date` objects, arrays and objects instead of `Map` and `Set`. - Use `actionSanitizer` and `stateSanitizer` to replace large blobs or sensitive fields in what the extension records, and `actionsDenylist` to hide noisy actions such as high-frequency ticks. - Lower `maxAge` if a long history makes the extension slow. - Give the store a `name` when a page hosts more than one, so the extension lists them separately. - Use descriptive action types such as `cart/itemAdded`; the history is read by type, and a log of generic `UPDATE` actions is hard to navigate. ## Production considerations `configureStore` leaves `devTools` enabled unless you disable it, so a production build will connect to the extension on a user's machine if it is installed, exposing the state and action history to anyone who opens it. Teams that store sensitive data commonly pass `devTools: process.env.NODE_ENV !== 'production'`, or keep it on with sanitizers when production debugging is worth the exposure. ## Interview summary - the extension records actions and resulting states through a store enhancer; - jump restores a recorded state, skip recomputes by re-running reducers; - replay depends on pure reducers, immutable updates and serialisable data; - thunks and other middleware side effects are not replayed.
- Jumping back in Redux DevTools shows the latest data on every earlier step. What is the likely cause?State is being mutated somewhere, so every recorded snapshot shares the same objects and changes along with the current one. Look for reducers written without Immer that push or assign into existing state, or components editing objects they selected. Redux Toolkit's development immutability check throws at the mutation site.
- How do you find which code dispatched a particular action in Redux DevTools?Open the action's Trace tab, which shows the stack trace captured at dispatch time. `configureStore` enables trace capture in development when DevTools are on; with a hand-built store you enable the extension's `trace` option yourself.
saying these in an interview costs you the question
- Time travel replays thunks and their network requests
- Jumping to an action re-runs every reducer from the start
- Reducers may call Date.now() because DevTools records the state anyway
- Skipping an action only hides it without changing later states
- Mutating state is harmless as long as the UI updates