skip to content

React Integration and DevTools

Wiring the store into React with Provider, useSelector and useDispatch, plus what DevTools time travel gives you while debugging. Interviewers ask about selector granularity, because a badly scoped useSelector re-renders half the application.

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

explore

questions

5

In React-Redux, what do Provider, useSelector and useDispatch each do when you connect a Redux store to a React app?

level: juniorimportance: must knowfreq 74%

answer

  1. one component, two everyday hooks
  2. context carries the store
  3. read and subscribe, then compare
  4. dispatch is the store's own

basics

~20 s

Provider makes the Redux store available to every component beneath it. useSelector reads a value from the store and re-renders the component when that value changes. useDispatch returns the store's dispatch function so components can send actions.

solid answer

~40 s

`<Provider store={store}>` is rendered once near the root; it puts the store in a React context and sets up the subscription that the hooks attach to. `useSelector(selector)` calls your selector with the current state, returns the result, and **subscribes**: after each dispatched action it re-runs the selector and re-renders the component only if the new result differs from the previous one, by `===` unless you pass an equality function. `useDispatch()` returns the store's `dispatch`, a stable reference for the life of the store, which you call with an action or a thunk. A component using either hook outside a `Provider` throws *could not find react-redux context value*. In TypeScript, React-Redux 9.1 added `useSelector.withTypes<RootState>()` and `useDispatch.withTypes<AppDispatch>()` to create pre-typed hooks once.

code

tsx · 23 lines
tsx
import { Provider, useDispatch, useSelector } from 'react-redux'
import { createRoot } from 'react-dom/client'
import { store, type RootState, type AppDispatch } from './store'
import { itemAdded, selectCartCount } from './cartSlice'

export const useAppSelector = useSelector.withTypes<RootState>()
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()

function CartButton({ productId }: { productId: string }) {
  const count = useAppSelector(selectCartCount)
  const dispatch = useAppDispatch()
  return (
    <button onClick={() => dispatch(itemAdded(productId))}>
      Add to cart ({count})
    </button>
  )
}

createRoot(document.getElementById('root')!).render(
  <Provider store={store}>
    <CartButton productId="p-1" />
  </Provider>,
)

go deeper

for a junior

Recall the three roles: Provider exposes the store once, useSelector reads and subscribes, useDispatch returns dispatch for sending actions and thunks.

for a middle

Explain that useSelector re-runs the selector after each dispatch and compares results with ===, and why useStore does not subscribe.

for a senior

Set up typed hooks with withTypes, test components with a real store in a Provider, and keep state flowing through selectors rather than ad hoc store reads.

for a principal

Decide how the store is provided across app shells, tests and server rendering, including when a per-request store and serverState are needed.

## The three pieces React-Redux is the official binding between a Redux store and React components. Day to day you use one component and two hooks. | API | Where it is used | What it does | |---|---|---| | `<Provider store={store}>` | once, near the root of the tree | exposes the store to every descendant through React context | | `useSelector(selector)` | any function component | reads a value from the store and subscribes to changes | | `useDispatch()` | any function component | returns the store's `dispatch` | ## Provider `Provider` takes the store created by `configureStore` and renders its children. Internally it places the store and a root **subscription** object into React-Redux's context, so hooks anywhere below can find both. - There is normally exactly **one** `Provider`, wrapping the whole app, because Redux has one store. - It also accepts `serverState`, used during hydration after server rendering so the first client render reads the same state the server used. - A component that calls `useSelector` or `useDispatch` with no `Provider` above it throws *"could not find react-redux context value; please ensure the component is wrapped in a <Provider>"*. In tests, wrap the component under test in a `Provider` with a test store. The context carries the store **instance**, which never changes; state changes flow through the subscription, not through context re-renders. ## useSelector `useSelector(selector, equalityFnOrOptions?)` does two jobs: 1. **Read.** During render it calls `selector(state)` and returns the result. 2. **Subscribe.** It registers with the store; after every dispatched action it runs the selector again and compares the new result with the previous one. If they differ, the component re-renders; if they are equal, nothing happens. The default comparison is strict reference equality, `===`. That makes selectors that return an existing piece of state — `state.cart.items`, `state.user.name` — cheap and precise. A selector that builds a new object or array on every call looks changed every time, which is a separate topic worth knowing well. You can call `useSelector` several times in one component, one per value; each call is its own subscription. ## useDispatch `useDispatch()` returns the store's `dispatch` function. It is the same function reference for as long as the store is the same, so it is safe to use in effect dependency arrays and does not need memoising itself. ```tsx const dispatch = useAppDispatch() <button onClick={() => dispatch(itemAdded(product))}>Add</button> ``` With Redux Toolkit's default middleware, `dispatch` also accepts thunks, so `dispatch(fetchCart())` works the same way. ## Pre-typed hooks In TypeScript, repeating `(state: RootState) => ...` and `AppDispatch` at every call site is noisy and error-prone. React-Redux 9.1 added `.withTypes()`: ```ts export const useAppSelector = useSelector.withTypes<RootState>() export const useAppDispatch = useDispatch.withTypes<AppDispatch>() export const useAppStore = useStore.withTypes<AppStore>() ``` Components then import `useAppSelector` and `useAppDispatch`. Typing `useDispatch` with the store's own dispatch type is what makes dispatching thunks type-check. ## useStore, the rarely needed fourth `useStore()` returns the store itself. It does **not** subscribe, so reading `store.getState()` during render shows stale data after updates. It exists for rare cases such as injecting reducers; prefer `useSelector` for reading state. ## Batching When one dispatch changes values read by several components, React 18 and later batch the resulting updates into one render pass automatically. React-Redux 9 still exports `batch()`, but only as a deprecated no-op kept for older code. ## Testing components that use the hooks Because the hooks read the store from context, a component test wraps the component in a `Provider` with a **real store** built from the app's reducers, for example `render(<Provider store={makeStore(preloadedState)}><CartButton productId="p-1" /></Provider>)`. The test then asserts on the rendered output and on `store.getState()` after a click. - Create a **fresh store per test**; a store shared across tests leaks state between them. - Prefer this over mocking `useSelector`, which only tests the mock and skips the selector and reducer wiring the component depends on. - A small `renderWithProviders` helper keeps the setup to one line per test. ## Putting it together - create the store with `configureStore`; - wrap the app in `<Provider store={store}>`; - read with `useAppSelector(selectSomething)`; - write with `useAppDispatch()` and action creators or thunks. An interview answer should cover all three roles, name the default `===` comparison, and mention the typed hooks.

  • Why should a React-Redux component not read state with useStore().getState() during render?
    `useStore` returns the store without subscribing to it, so the component is not re-rendered when state changes and keeps showing whatever it read last. `useSelector` both reads and subscribes, so it is the right tool for rendering from store state.
  • Is the function returned by React-Redux's useDispatch stable between renders?
    Yes. It is the store's own `dispatch`, so its reference stays the same as long as the `Provider` keeps the same store. Listing it in a `useEffect` dependency array therefore does not cause extra effect runs.

saying these in an interview costs you the question

  • Each component needs its own Provider around it
  • useSelector deep-compares the selected value by default
  • useDispatch returns a new function on every render
  • useStore().getState() keeps the component up to date
  • Provider re-renders the whole tree on every state change
open as a page

In React-Redux, why does useSelector(state => ({ count: state.count, user: state.user })) re-render its component after every dispatched action?

level: middleimportance: must knowfreq 70%

basics

~20 s

The selector builds a new object on every run, and useSelector compares results with === by default, so after any dispatch the result looks changed and the component re-renders. Select each field separately, or pass shallowEqual as the equality function.

open as a page

In React-Redux 9, what does connect() do for render performance that useSelector does not, and why do most codebases still prefer hooks?

level: middleimportance: should knowfreq 42%

basics

~20 s

connect wraps the component in React.memo and shallow-compares the mapState result and incoming props, so it skips parent-driven renders. useSelector compares only its own result and never blocks parent renders, yet hooks need far less code and typing.

open as a page

How does Redux DevTools time-travel debugging work, and what in your reducers or state stops it from replaying correctly?

level: middleimportance: should knowfreq 38%

basics

~20 s

Redux 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.

open as a page

In React-Redux, a 500-row table re-renders every row when one row's status changes; how do you scope useSelector so only that row re-renders?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Have the parent select only the row ids, wrap Row in React.memo and pass each an id, and let each Row select its own entity by id. A status change then changes only that row's selected value, so only it re-renders.

open as a page