Keep a Redux app fast and tidy — reusable selectors, memoized derived data with createSelector, and the everyday rules that keep re-renders to a minimum.
Why: a selector is just the little function you pass to useAppSelector to pick a value out of the store. When the same value is read in several components, give that function a name and export it from the slice — then every component reads through the same tested function, and if the state shape changes you fix it in one place. Note: selectors take the whole RootState and return the piece you want.
// app/counterSlice.ts — export selectors next to the slice
import type { RootState } from './store'
// A selector is just a named function: whole state in, one piece out
export const selectCount = (state: RootState) => state.counter.value
// app/CounterDisplay.tsx — components read through the named selector
'use client'
import { useAppSelector } from '../hooks'
import { selectCount } from '../counterSlice'
export default function CounterDisplay() {
// If the state shape ever changes, you fix it once in the selector
const count = useAppSelector(selectCount)
return <p>Count: {count}</p>
}Why: sometimes a component needs a value COMPUTED from the store — a filtered list, a total, a count. Doing that work inside useAppSelector recalculates it on every render, and returning a new array each time forces needless re-renders. createSelector fixes both: it remembers ("memoizes") the result and only recomputes when its inputs actually change. Note: this is the same idea as useMemo, but for store selectors, and reusable across components.
// app/todosSlice.ts
import { createSelector } from '@reduxjs/toolkit'
import type { RootState } from './store'
// Input selectors: the raw pieces this computation depends on
const selectTodos = (state: RootState) => state.todos.items
const selectFilter = (state: RootState) => state.todos.filter
// createSelector runs the last function only when an input changes,
// and caches the result in between — no wasted work, stable reference.
export const selectVisibleTodos = createSelector(
[selectTodos, selectFilter],
(todos, filter) => {
if (filter === 'active') return todos.filter((t) => !t.done)
if (filter === 'done') return todos.filter((t) => t.done)
return todos
},
)
// Use it exactly like any selector:
// const visible = useAppSelector(selectVisibleTodos)Why: most Redux performance problems come from a handful of avoidable habits. Internalize these four and your app stays fast without special tooling. Note: none of this is exotic — it is just selecting narrowly and computing carefully.
// 1. Select the SMALLEST value you need — narrower = fewer re-renders.
const count = useAppSelector((s) => s.counter.value) // ✓ a number
// const whole = useAppSelector((s) => s.counter) // ✗ re-renders more
// 2. Don't build a NEW object/array inside a selector without memoizing —
// a fresh reference every time looks like "changed" and re-renders.
// Wrong:
// useAppSelector((s) => s.todos.items.filter((t) => !t.done))
// Right: a createSelector like the previous topic.
// 3. Read several values in several calls, not one object:
const name = useAppSelector((s) => s.user.name)
const email = useAppSelector((s) => s.user.email)
// 4. Keep only truly SHARED state in Redux. Local UI state — an input's
// text, whether a dropdown is open — belongs in useState, not the store.