Learn Redux - Selectors & Hooks (useSelector/useDispatch)
Episode 5 of 23

Learn Redux - Selectors & Hooks (useSelector/useDispatch)

This episode teaches how components read and change state: useSelector for reading, useDispatch for dispatching actions. You'll understand selectors as pure functions, when re-renders happen with strict equality, and how to use shallowEqual for complex data.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

The store and slice exist, but they're not connected to the UI yet. Episode 5 bridges the two: hooks from react-redux that connect React components to the Redux store. This is the moment when the state that's been living inside the store finally shows up on the screen.

We'll cover useSelector for reading state, useDispatch for dispatching actions, the principle of selectors as pure functions, and when and how re-renders happen — including using shallowEqual to prevent excessive re-renders.

useSelector and useDispatch

Reading State with useSelector

useSelector(selector) accepts a selector function and returns the selected piece of state. React Redux automatically subscribes to the store and re-renders the component whenever the selector result changes.

JSReading counter state
import { useSelector, useDispatch } from "react-redux"
 
function Counter() {
  const count = useSelector((state) => state.counter.count)
  const dispatch = useDispatch()
 
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => dispatch(increment())}>+</button>
    </div>
  )
}

useSelector((state) => state.counter.count) reads the count value from the store. useDispatch() returns a ready-to-use dispatch function, and dispatch(increment()) sends an action from the counter slice.

Selectors: Pure Functions for Selecting State

A selector is a pure function: it receives the entire state and returns the selected data. Because it's pure, a selector can be tested, rewritten, and combined easily.

JSA selector written separately
const selectCount = (state) => state.counter.count

Writing a selector as a standalone constant allows reuse across many components and forms the basis of createSelector memoization, which we'll cover in episode 13.

When Re-renders Happen

Strict Equality as the Key

React Redux compares the selector result with the previous one using strict equality. The component only re-renders if the value changed:

JSPrimitive selectors are safe
const name = useSelector((state) => state.user.name)

state.user.name is a string — a primitive. Strings are compared by value, so re-renders only happen when the name actually changes.

The Danger of Returning New Objects

The problem appears when a selector returns a freshly created object or array every time:

JSA pattern that triggers infinite re-renders
const user = useSelector((state) => ({
  name: state.user.name,
  age: state.user.age,
}))

{ name, age } is a new object on every selector evaluation, so the result is always different under strict equality — the component re-renders endlessly. The full solution is in episode 13, but there's a shortcut: shallowEqual below.

Selector Performance

Selecting Minimal Data

The smaller the selected data, the less often re-renders happen. Select only the fields the component actually uses:

JSSelect minimal data
const total = useSelector((state) => state.cart.items.reduce((s, i) => s + i.qty, 0))

This pattern does trigger a re-render on every cart content change, but it only returns a single number, so cross-component state comparisons stay cheap.

shallowEqual for Objects and Arrays

When a component needs several fields at once, combine them with shallowEqual:

JSCombining selectors with shallowEqual
import { useSelector, shallowEqual } from "react-redux"
 
const { name, age } = useSelector(
  (state) => ({
    name: state.user.name,
    age: state.user.age,
  }),
  shallowEqual,
)

shallowEqual compares each top-level field individually rather than comparing object references. If name and age are the same as before, there's no re-render — a direct fix for the new-object problem above.

Combining Several Selectors

For many values, a single useSelector call may select several fields at once as long as they're combined with the right comparator:

JSCombining fields
const status = useSelector((state) => state.posts.status)
const items = useSelector((state) => state.posts.items)

Two separate selector calls are also valid and actually easier to predict. Use separate calls for values that rarely change, and shallowEqual when they must be grouped into a single object.

Conclusion

Episode 5 closes the loop on basic state management: components read state with useSelector, change it with useDispatch, and re-render only when the selector result actually changes. Performance is already becoming a concern — and it will be a major theme in episode 13.

Key takeaways:

  • useSelector(selector) reads state and subscribes to changes.
  • useDispatch() returns a function for dispatching actions.
  • Selectors are pure functions that can be reused.
  • Strict equality determines when re-renders happen.
  • Don't return new objects from a selector without a comparator.
  • shallowEqual groups several fields without excessive re-renders.

In the next episode, episode 6, you'll step into the async world: createAsyncThunk — creating actions that fetch API data, understanding the pending, fulfilled, and rejected lifecycle, managing loading, succeeded, and failed statuses, and aborting requests when a component unmounts.

Learn Redux - Selectors & Hooks (useSelector/useDispatch) | Learn Redux