Back to React
React
medium
mid

How do you subscribe to a Redux store from a React component?

In React you don't call store.subscribe directly — useSelector subscribes a component to the slice it reads and re-renders it on change. Under the hood the <Provider> and useSelector use store.subscribe. Vanilla Redux exposes store.subscribe(listener) returning an unsubscribe function.

4 min read·~6 min to think through

"Subscribe" has two answers — the vanilla Redux API and the React way — and a good answer covers both.

Vanilla Redux — store.subscribe

The store exposes a low-level subscription API:

js
const unsubscribe = store.subscribe(() => {
  console.log("state changed:", store.getState());
});
// later:
unsubscribe();

subscribe(listener) registers a callback fired after every dispatched action, and returns an unsubscribe function. Note it doesn't tell you what changed — you call getState() and diff yourself. You rarely use this directly in a React app.

The React way — useSelector

With react-redux, you don't manually subscribe. You:

  1. Wrap the app in <Provider store={store}> — this makes the store available via context and manages subscriptions.
  2. In a component, call useSelector:
jsx
function Counter() {
  const value = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();
  return <button onClick={() => dispatch(increment())}>{value}</button>;
}

useSelector subscribes the component to the store under the hood. After every dispatch it runs your selector, compares the result to the previous one (strict === by default), and re-renders only if that slice changed. So a component re-renders for the data it actually reads — not for every action.

Key points to mention

  • Selector granularity — select the smallest slice you need. Selecting a whole object (or returning a new object/array each call) defeats the === check and causes extra re-renders. Use shallowEqual or memoized selectors (reselect / createSelector) for derived data.
  • Cleanup is automaticuseSelector unsubscribes on unmount; manual store.subscribe must be unsubscribed yourself.
  • Modern internals — react-redux uses React's useSyncExternalStore to subscribe safely and tear-free with concurrent rendering.

The framing

"Vanilla Redux gives you store.subscribe(listener), which fires on every action and returns an unsubscribe function — but in React you almost never use it directly. You wrap the app in <Provider> and use useSelector, which subscribes the component to the store, re-running your selector after each dispatch and re-rendering only when the selected slice changes by reference. The skill is writing narrow selectors so that comparison actually saves renders."

Follow-up questions

  • What does store.subscribe's return value do?
  • How does useSelector decide whether to re-render?
  • Why can returning a new object from a selector hurt performance?
  • What is useSyncExternalStore and why does react-redux use it?

Common mistakes

  • Calling store.subscribe manually inside components instead of using useSelector.
  • Forgetting to unsubscribe a manual store.subscribe listener.
  • Selecting a whole object or returning a new array/object, defeating the === check.
  • Thinking useSelector re-renders on every action — it only does when the slice changes.

Performance considerations

  • useSelector re-runs every selector after every dispatch — keep selectors cheap and narrow. Memoized selectors (reselect) avoid recomputing derived data and prevent reference-identity churn that triggers re-renders.

Edge cases

  • Selector returning a freshly computed object every call — needs reselect/memoization.
  • Multiple useSelector calls in one component — each subscribes independently.
  • Subscribing outside React (e.g. logging middleware) — vanilla subscribe is appropriate.

Real-world examples

  • A header badge using useSelector(state => state.cart.count) re-renders only when the count changes.
  • Logging middleware or analytics using vanilla store.subscribe outside the React tree.

Senior engineer discussion

Seniors distinguish the vanilla subscribe API from the react-redux hooks layer, explain useSelector's reference-equality re-render rule, stress narrow/memoized selectors, and mention useSyncExternalStore as the modern, concurrent-safe subscription mechanism.

Related questions