How to subscribe a store in redux
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.
"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:
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:
- Wrap the app in
<Provider store={store}>— this makes the store available via context and manages subscriptions. - In a component, call
useSelector:
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. UseshallowEqualor memoized selectors (reselect/createSelector) for derived data. - Cleanup is automatic —
useSelectorunsubscribes on unmount; manualstore.subscribemust be unsubscribed yourself. - Modern internals — react-redux uses React's
useSyncExternalStoreto 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.