skip to content

Store and Data Flow

One store, one direction: dispatch an action, the reducer returns the next state, the view re-renders. Interviewers ask you to trace that loop because it is the fastest way to tell whether you understand Redux or have only used it.

part ofReduxoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In a React app backed by Redux, what happens, step by step, from a user clicking an Add button until the screen shows the change?

level: juniorimportance: must knowfreq 75%

answer

  1. one store, one direction
  2. dispatch is the only way in
  3. root reducer computes the next state
  4. listeners run after the reducer returns
  5. UI rereads state and compares

basics

~20 s

The click handler dispatches an action; the store runs the root reducer with the current state and that action, saves the result, then calls every subscriber, and the subscribed UI rereads state and re-renders what changed.

solid answer

~40 s

The handler calls something like `dispatch({ type: 'cart/itemAdded', payload: item })`. After any middleware has passed it on, the store checks that the action is a plain object with a string `type`, calls the root reducer with the current state and the action, and keeps whatever the reducer returns as the new state. Only then does it call every listener registered with `subscribe`, passing no arguments. In a React app, react-redux is one of those listeners: it reads `getState()`, re-runs the components' selectors and re-renders only the components whose selected value changed. The view never writes state itself; to change anything it dispatches another action, which starts the same loop again.

code

ts · 23 lines
ts
import { legacy_createStore as createStore, type UnknownAction } from 'redux'

interface CartState {
  items: string[]
}

function cartReducer(state: CartState = { items: [] }, action: UnknownAction): CartState {
  console.log('2. reducer runs:', action.type)
  if (action.type === 'cart/itemAdded') {
    return { items: [...state.items, action.payload as string] }
  }
  return state
}

const store = createStore(cartReducer)

store.subscribe(() => {
  console.log('3. listener runs, items =', store.getState().items)
})

console.log('1. click handler dispatches')
const returned = store.dispatch({ type: 'cart/itemAdded', payload: 'apple' })
console.log('4. dispatch returned', returned)

go deeper

for a junior

Recite the loop in order: event, dispatch, reducer, new state saved, listeners called, UI rereads and re-renders. Say clearly that dispatch is the only way to change state.

for a middle

Add the mechanics: dispatch validates the action, the reducer runs synchronously, listeners get no arguments, and the UI binding compares selected values to decide what re-renders.

for a senior

Point out where the loop gets bent in production: middleware can intercept or delay actions, and many sequential dispatches mean many listener passes and many render checks.

for a principal

Frame the one-way loop as a trade: a single audited write path and a replayable history, bought with indirection and ceremony that small, local UI state rarely needs.

## The loop in one line Redux is built around a **single store** and a **one-way data flow**. The view never edits state. It *describes* what happened by dispatching an **action**, a plain object with a `type` field, and the store works out the consequences. Tracing one click through that loop is the quickest way to show you understand how Redux behaves at runtime rather than just which functions to call. ## The steps, in order 1. **The event fires.** The user clicks Add, and the component's `onClick` handler runs. 2. **An action is dispatched.** The handler calls `dispatch({ type: 'cart/itemAdded', payload: item })`. In a React app that `dispatch` usually comes from react-redux, but it is the store's own `dispatch`, possibly wrapped by middleware. 3. **Middleware sees it first (if any).** Middleware installed on the store can log the action, delay it, or swallow it. This is optional; a bare store has none. 4. **The store validates the action.** In Redux 5 the core `dispatch` throws if the action is not a plain object, if `type` is `undefined`, or if `type` is not a string. 5. **The root reducer runs.** The store calls `rootReducer(currentState, action)` **synchronously**. The reducer returns the next state, a new object for the parts that changed, and the same references for everything else. 6. **The store saves the result.** Whatever the reducer returned becomes `currentState`. The store does not diff or validate it. 7. **Every listener is called.** Each function registered with `store.subscribe` is called, with **no arguments**. A listener that wants the new state calls `store.getState()`. 8. **The view updates.** react-redux's subscription reruns the selectors of mounted components, compares each result with the previous one, and re-renders the components whose selected data changed. 9. **`dispatch` returns.** With no middleware, it returns the action object you passed in. ## What the store guarantees inside `dispatch` - **Order**: the reducer finishes before any listener runs, so a listener never sees a half-computed state. - **Synchrony**: with a plain action and no async middleware, the new state is readable on the very next line after `dispatch`. - **Single writer**: `dispatch` is the only way to change the state. There is no `setState` on the store. - **Guard rails**: while the reducer is running, `getState`, `subscribe` and `dispatch` throw, because the reducer is supposed to work only from its arguments. ## Who does what | Step | Runs in | Allowed to change state? | |---|---|---| | Click handler | Component | No, it only dispatches | | Middleware | Store's dispatch chain | No, it can only dispatch or pass the action on | | Root reducer | Store | Yes, by returning a new value | | Listeners and UI | Subscribers | No, they read and render | ## Why the direction matters - **Predictability**: every state change has a named cause in the action log, so "why did this value change?" has one place to look. - **Debuggability**: because changes happen one at a time, in order, through pure reducers, tools can record the action sequence and replay it. - **Decoupling**: the button does not need to know which parts of the state react to `cart/itemAdded`. Several reducers can respond to the same action. - **Testability**: the reducer step is a plain function from `(state, action)` to state, so you can test it without a UI. ## Where the loop gets bent in real apps The textbook loop assumes a plain object action and no middleware. Production code adds three twists, and interviewers like to hear that you know them: - **Async work happens outside the loop.** A click that needs data from the server does not put a Promise into the reducer. Middleware such as redux-thunk runs the request and dispatches ordinary actions (`started`, `succeeded`, `failed`) as it progresses. Each of those is a full trip around the loop. - **Many dispatches mean many passes.** Every dispatch runs the reducer and notifies every listener. Five dispatches in a row are five passes, each rerunning every subscribed component's selector and possibly rendering. One meaningful event should usually be one action. - **Several reducers can respond to one action.** The action says what happened; the root reducer lets every slice decide whether it cares. The click that adds an item can update the cart, a "recently added" list and a counter in one pass. ## Answers that lose points 1. "The component calls `setState` on the store." There is no such method; the store has `dispatch`, `getState`, `subscribe` and `replaceReducer`. 2. "The reducer calls the API and then updates state." Reducers must be pure and synchronous; side effects belong in middleware or in the code that dispatches. 3. "Redux re-renders the whole app." The store only notifies; the UI binding decides which components re-render, based on what each one selected. ## The Redux 5 footnote on `createStore` In Redux 5 the core `createStore` is marked **deprecated**. It still works and is not going to be removed, but the maintainers want new code to use `configureStore` from Redux Toolkit, which builds on `createStore`. If you want the core function without the strikethrough in your editor, `legacy_createStore` is the same function under another name. Either way, the loop described above is exactly what runs.

  • What does store.dispatch return when no middleware is installed?
    It returns the same action object you passed in. Middleware can change that: redux-thunk, for example, returns whatever the thunk function returns, often a promise, so callers can `await dispatch(someThunk())`. Code that relies on the return value should therefore know which middleware is installed.
  • Is the new state readable immediately after dispatch returns?
    Yes, for a plain action. The core `dispatch` runs the reducer synchronously and stores the result before it returns, so `store.getState()` on the next line already reflects the action. React may render the change slightly later, because React schedules renders, but the store itself is already updated.
  • In Redux 5, should new code still call createStore?
    It works, and the maintainers say it will not be removed, but it is marked deprecated to steer people to Redux Toolkit's `configureStore`, which builds on it and adds sensible defaults. `legacy_createStore` is the same function without the deprecation marker, useful for learning or for legacy code you are not migrating yet.

It works like a bank ledger. You cannot edit your balance directly; you hand the teller a slip describing the transaction (the action), the teller applies the bank's fixed rules (the reducer) and posts the new balance, and only then does the display board everyone is watching refresh.

saying these in an interview costs you the question

  • The component writes to the store's state directly and Redux notices the change
  • Subscribers are notified before the reducer computes the new state
  • A plain dispatch is asynchronous, so state changes on the next tick
  • Each component gets its own Redux store that syncs with the others
  • The listener receives the new state as its argument
open as a page

In Redux, what does the preloadedState argument to createStore do, and how does it interact with each reducer's default state?

level: middleimportance: should knowfreq 45%

basics

~20 s

preloadedState is the store's starting state, used to hydrate from the server or restore a saved session; it wins over a reducer's default, and with combineReducers any slice it omits falls back to that slice reducer's own default.

open as a page

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%

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.

open as a page

When does a Redux store call the listeners registered with store.subscribe, and do they fire when the reducer returns the same state?

level: middleimportance: should knowfreq 40%

basics

~20 s

A Redux store calls every subscribed listener after every dispatch, even when the reducer returned the identical state object; the store never compares old and new state, so skipping unchanged data is the subscriber's job.

open as a page

A server-rendered React app exports its Redux store as a module-level singleton, and users occasionally see another user's data. Why, and how should store creation and hydration work?

level: seniorimportance: should knowfreq 35%

basics

~20 s

A module-level store is created once per server process and shared by every concurrent request, so one user's dispatches leak into another's render; create a fresh store per request, serialize its state into the page safely, and preload the client's own store from it.

open as a page