mrkeyoor.com_
Thu 06 Aug 02:42 UTC
npmWeb Frontendupdated 06 Aug 2026

reselect

Reselect builds memoized selector functions. You give createSelector a list of input selectors that pull raw slices out of your state, plus a result function that turns those slices into derived data. The generated selector only re-runs the result function when one of the inputs changes by reference, and otherwise hands back the exact same object it returned last time. That reference stability is the real point: React-Redux and React skip re-rendering when a value is identical, so a selector that filters or sorts an array stops causing renders on every unrelated dispatch. It is most associated with Redux but takes plain functions and works with any immutable state. It also ships inside Redux Toolkit, so many people use it without ever installing it.

Verdict

Still the default answer for derived state in Redux apps, and v5's weakMapMemoize removed the cache-size-1 trap that made parameterized selectors painful. If you use Redux Toolkit you already have it, and if you do not use Redux you probably do not need it.

API stability4/5v5 in December 2023 changed the default memoizer and renamed defaultMemoize to lruMemoize, which was a real migration; since then 5.1 and 5.2 have been additive and the core createSelector signature has been unchanged for years.
Docs5/5reselect.js.org has a dedicated API page per export, a 'how does it work' explainer, an FAQ, and documentation of the dev-mode checks, backed by the Redux docs page on deriving data with selectors.
Maintenance4/5Owned by the Redux team, pushed July 2026 with 31 open issues (56 counting PRs); releases are infrequent (5.1.1 in mid-2024, 5.2.0 in May 2026) but the library is small and largely finished.
Ecosystem5/5Around 42 million weekly downloads, shipped inside Redux Toolkit, and the pattern it defines is what React-Redux docs, entity adapters, and most Redux tutorials assume.

Use it if

  • You store normalized state and components need derived views of it: filtered todo lists, sorted tables, totals, or joins across two slices that would otherwise recompute on every dispatch
  • A component re-renders constantly because useSelector returns a fresh array or object each time; a memoized selector returns the same reference until the underlying data actually changes
  • The derivation is genuinely expensive (large array transforms, grouping thousands of rows) and you want it computed once per state change rather than once per subscribed component
  • You want composable derivation: selectors take other selectors as inputs, so selectVisibleTodoCount can build on selectVisibleTodos without duplicating the filter logic
  • You call the same selector with different arguments across list items, like selectItemById(state, id); the v5 default weakMapMemoize caches per argument instead of only remembering the last call
Skip it if

Setup reality

npm install reselect is about as painless as it gets: zero runtime dependencies, roughly 7KB minified, dual CJS and ESM, TypeScript types included and TypeScript 4.7 or later required. The real setup cost is behavioral. In development, createSelector runs two checks the first time each selector is called (inputStabilityCheck calls your input selectors twice and warns if results differ, identityFunctionCheck warns if the result function just returns its input), so a working app can suddenly print console warnings after upgrading; you turn them off per selector with devModeChecks or globally with setGlobalDevModeChecks. v5 also changed the default memoizer from the old cache-size-1 LRU to weakMapMemoize, so tuning advice from older articles about maxSize and createSelectorCreator no longer applies, and defaultMemoize was renamed to lruMemoize. If you are on Redux Toolkit you already have it and should import from @reduxjs/toolkit rather than installing separately.

Patterns

Create a selector that skips recomputationbasic-memoized-selector

import { createSelector } from 'reselect'

const selectTodos = (state: RootState) => state.todos

export const selectCompletedTodos = createSelector(
  [selectTodos],
  todos => todos.filter(t => t.completed)
)

selectCompletedTodos(state) === selectCompletedTodos(state) // true

The array form for input selectors is the documented style and gives better TypeScript inference than passing them variadically. Identical references out means React-Redux will not re-render subscribers.

Build selectors out of other selectorscompose-selectors

const selectFilter = (state: RootState) => state.filters.status

export const selectVisibleTodos = createSelector(
  [selectTodos, selectFilter],
  (todos, status) =>
    status === 'all' ? todos : todos.filter(t => t.status === status)
)

export const selectVisibleCount = createSelector(
  [selectVisibleTodos],
  todos => todos.length
)

Composition is the point: selectVisibleCount only recomputes when selectVisibleTodos returns a new array. Do not inline the filter logic in both places, or the two will drift and both will recompute independently.

Pass an id or other argument through to the selectorparameterized-selector

export const selectTodoById = createSelector(
  [selectTodos, (state: RootState, id: string) => id],
  (todos, id) => todos.find(t => t.id === id)
)

selectTodoById(state, 'abc')

Every input selector receives all the arguments, so the second one is just an extractor. Pass primitives: an object argument like { id } creates a new reference each call and weakMapMemoize will miss the cache every time.

Stop annotating RootState on every input selectortyped-selector-creator

import { createSelector } from 'reselect'

export const createAppSelector = createSelector.withTypes<RootState>()

export const selectPostById = createAppSelector(
  [state => state.posts.entities, (state, id: number) => id],
  (entities, id) => entities[id]
)

Added in 5.1. Parameter inference only works when the input selectors are wrapped in an array; with the variadic form you have to annotate extra parameters manually because of a TypeScript limitation.

Select several values as one objectstructured-selector

import { createStructuredSelector } from 'reselect'

export const selectHeaderProps = createStructuredSelector({
  userName: (state: RootState) => state.user.name,
  unread: (state: RootState) => state.alerts.unreadCount,
})

const { userName, unread } = selectHeaderProps(state)

The returned object is memoized as a whole, so it stays reference-stable until one of the fields changes. Doing the same thing by hand with an object literal in useSelector produces a new object every render and re-renders on every action.

Keep the same reference when the value is equalstabilize-new-references

import { createSelector, weakMapMemoize } from 'reselect'
import { shallowEqual } from 'react-redux'

export const selectTodoIds = createSelector(
  [selectTodos],
  todos => todos.map(t => t.id),
  {
    memoize: weakMapMemoize,
    memoizeOptions: { resultEqualityCheck: shallowEqual },
  }
)

A result function that maps or spreads returns a brand new array on every recomputation, so consumers re-render even when nothing meaningful changed. resultEqualityCheck compares the new result against the previous one and keeps the old reference when they match, at the cost of running the comparison.

Use the old LRU memoizer with a bounded cachelru-cache-size

import { createSelector, lruMemoize } from 'reselect'

export const selectExpensiveReport = createSelector(
  [selectRows, (state: RootState, month: string) => month],
  (rows, month) => buildReport(rows, month),
  {
    memoize: lruMemoize,
    memoizeOptions: { maxSize: 12 },
  }
)

lruMemoize is the v4 defaultMemoize under a clearer name, and its default cache size is 1. Choose it over weakMapMemoize when you want a hard ceiling on retained results rather than a cache keyed by argument identity.

Apply the same memoization settings across a codebasecustom-selector-creator

import { createSelectorCreator, lruMemoize } from 'reselect'
import { isEqual } from 'lodash'

export const createDeepEqualSelector = createSelectorCreator({
  memoize: lruMemoize,
  memoizeOptions: { equalityCheck: isEqual, maxSize: 5 },
})

export const selectConfig = createDeepEqualSelector(
  [(state: RootState) => state.config],
  config => normalize(config)
)

Deep equality on inputs is a trade: you pay isEqual on every call to avoid a recomputation. It is worth it only when the result function is much more expensive than the comparison, which is rarer than people assume.

Turn off a development-mode warningsilence-dev-checks

import { createSelector, setGlobalDevModeChecks } from 'reselect'

// per selector
const selectThing = createSelector([selectRaw], derive, {
  devModeChecks: { inputStabilityCheck: 'never' },
})

// or globally, once at startup
setGlobalDevModeChecks({ identityFunctionCheck: 'never' })

The checks run only in development and by default only on the first call ('once'); 'always' is useful while debugging. Silence them only after you understand the warning, since inputStabilityCheck firing almost always means memoization is not working at all.

Find out whether a selector is actually memoizingdebug-recomputations

selectVisibleTodos(state)
selectVisibleTodos(state)

console.log(selectVisibleTodos.recomputations())            // 1
console.log(selectVisibleTodos.dependencyRecomputations()) // 1
selectVisibleTodos.resetRecomputations()
selectVisibleTodos.clearCache()

recomputations counts result function runs; dependencyRecomputations counts input selector runs, which is how you tell whether the argument memoization or the result memoization is the one missing. Both live on the selector object and are safe to read in tests.

Test the derivation without building a whole stateunit-test-result-function

import { selectVisibleTodos } from './selectors'

test('filters by status', () => {
  const todos = [{ id: '1', status: 'done' }, { id: '2', status: 'open' }]
  expect(selectVisibleTodos.resultFunc(todos, 'done')).toHaveLength(1)
})

resultFunc is the raw unmemoized function, so calling it takes the input selectors' outputs directly instead of a RootState. It also bypasses the cache, which is what you want in tests where memoization between cases would hide bugs.

Consume a selector from React-Reduxuse-in-react-component

import { useSelector } from 'react-redux'

function TodoList({ status }: { status: string }) {
  const todos = useSelector((state: RootState) =>
    selectTodosByStatus(state, status)
  )
  return <ul>{todos.map(t => <li key={t.id}>{t.text}</li>)}</ul>
}

With the v5 default memoizer a single shared selector handles many components with different arguments, so the old per-component makeSelector factory pattern is no longer needed. The inline arrow passed to useSelector is fine because the memoized selector inside it is what provides reference stability.

Alternatives

PackageRegistryPick it when
@reduxjs/toolkitnpmYou are writing Redux at all; it bundles this exact library plus createSlice and the entity adapter selectors you were about to hand-write.
proxy-memoizenpmYou want memoization based on which properties were actually touched, so you can write a plain function instead of splitting input selectors and a result function.
re-reselectnpmYou need an explicit cache key per selector call and want control over eviction rather than relying on weakMapMemoize's reference-keyed cache.