skip to content

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