reselect review
Reselect 5.3.0 creates memoized selectors from plain input functions and a result function, most often for Redux state. When extracted input references stay equal, the selector returns its existing result without recomputing, which also preserves reference equality for React-Redux. Version 5.3 adds maxSize to the default weakMapMemoize, a development warning for growing caches, faster cache-hit paths, and a TypeScript 5.6 floor. It removes the experimental unstable_autotrackMemoize export. Bounding createSelector requires limits on both its argument cache and result-function cache.
Reselect 5.3.0 installed in 0.4 seconds and bundled to 2.1 KB gzipped in our sandbox, so measured cost is low when derived-reference stability saves real work. Skip it for trivial property reads, mutable state, or Redux Toolkit projects that already receive createSelector.
We installed it
| Install | ✓ · 0.4s | 1 package on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · CommonJS package with exports map |
| Browser | 2.1 KB | gzipped (4.7 KB minified), bundled with esbuild |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does reselect install cleanly?
Yes. In a fresh container with an empty cache, npm install reselect finished in 0.4s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.
How much does reselect add to a browser bundle?
2.1 KB gzipped (4.7 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does reselect work with both ESM and CommonJS?
Yes. Both import 'reselect' and require('reselect') worked in Node 22 in our run. The package is published as CommonJS with an exports map.
Does reselect include TypeScript types?
Yes, type declarations ship inside the package, so no @types install is needed.
reselect or proxy-memoize: which should you use?
Pick proxy-memoize when proxy-memoize 3.x fits when property-access tracking should decide which nested changes invalidate derived state. Reselect 5.3.0 installed in 0.4 seconds and bundled to 2.1 KB gzipped in our sandbox, so measured cost is low when derived-reference stability saves real work.
When should you not use reselect?
Redux Toolkit is already installed only for Redux code. It exports createSelector, so a separate reselect dependency usually adds no API.
Discussed on
Use it if
- Reselect 5.3.0 fits state derivations that are expensive enough or return references whose stability matters to React rendering.
- Parameterized selectors need several cached argument combinations rather than the single-entry behavior used before Reselect 5.
- A team wants recomputation counters and development checks to diagnose unstable selector inputs.
- Redux Toolkit is not already the dependency supplying createSelector, or the standalone package is intentionally used outside Redux.
- Redux Toolkit is already installed only for Redux code. It exports createSelector, so a separate reselect dependency usually adds no API.
- The selector reads one cheap field such as state.user.name. A plain function is clearer and does not create cache state.
- Reducers mutate arrays or objects in place. Reference equality can say an input is unchanged and return stale derived data.
- Calls carry an unbounded stream of primitive arguments without maxSize. Version 5.3 added cache bounds and warnings because the default strategy can retain such entries.
- Code imports unstable_autotrackMemoize. Version 5.3 removes that experimental export; migrate to weakMapMemoize or lruMemoize first.
- The project is below TypeScript 5.6. Reselect 5.3's supported TypeScript matrix now starts at 5.6.
Setup reality
We installed reselect 5.3.0 in a fresh Node 22 Bookworm sandbox. npm completed in 0.4 seconds, left 1 package, and used 1 MB. npm audit reported 0 known vulnerabilities. The package has 0 direct and 0 peer dependencies, with 732 KB unpacked. It includes TypeScript declarations. package.json describes CommonJS with an exports map, and both require() and ESM import worked under Node 22.23.2.
Our broad browser import measured 4.7 KB minified and 2.1 KB gzipped in esbuild. If a project already uses Redux Toolkit, import createSelector from @reduxjs/toolkit rather than installing this package again. Outside Redux, selectors work with any immutable input objects. The important word is immutable: changing an array in place keeps its reference and can leave a cached result stale.
Input selectors receive every call argument and should only extract values. Put filtering, mapping, sorting, and object creation in the result function. A new array created inside an input selector defeats reference-based memoization. Use recomputations() and dependencyRecomputations() to distinguish a changing result input from changing selector arguments. Development stability checks can identify both an unstable input and an identity result function.
Version 5.3's weakMapMemoize maxSize uses generational cache swaps, not least-recently-used eviction. A createSelector output has two memoization levels, so set memoizeOptions.maxSize and argsMemoizeOptions.maxSize when both must be bounded. Use lruMemoize when eviction order or result equality is required. Fully clearing a selector also takes two calls: clearCache() and memoizedResultFunc.clearCache().
Patterns
Memoize a filtered collection filter-list
import { createSelector } from 'reselect';
const selectTodos = (state) => state.todos;
export const selectOpenTodos = createSelector([selectTodos], (todos) => todos.filter((todo) => !todo.done));Filtering belongs in the result function. Returning a new array from selectTodos would force recomputation on every call.
Pass an entity ID as an argument select-by-id
const selectItems = (state) => state.items;
const selectId = (_state, id) => id;
export const selectItem = createSelector([selectItems, selectId], (items, id) => items[id]);Every input selector receives every argument. Their parameter positions must remain compatible even when state is unused.
Create selectors pre-typed for RootState type-selectors
import { createSelector } from 'reselect';
import type { RootState } from './store';
export const createAppSelector = createSelector.withTypes<RootState>();
export const selectNames = createAppSelector([(state) => state.users], (users) => users.map((user) => user.name));Array-form input selectors give withTypes the strongest inference. Reselect 5.3 supports TypeScript 5.6 and newer.
Bound both selector cache levels bound-cache
const selectWindow = createSelector(
[selectItems, (_state, start) => start, (_state, _start, end) => end],
(items, start, end) => items.slice(start, end),
{ memoizeOptions: { maxSize: 100 }, argsMemoizeOptions: { maxSize: 100 } },
);Version 5.3 keeps separate argument and result caches. weakMapMemoize swaps generations at the limit rather than evicting one LRU item.
Choose least-recently-used eviction use-lru
import { createSelector, lruMemoize } from 'reselect';
const selectIds = createSelector([selectTodos], (todos) => todos.map((todo) => todo.id), {
memoize: lruMemoize,
memoizeOptions: { maxSize: 8 },
});lruMemoize is the documented choice when eviction order matters. Keep the cache smaller than the argument diversity you truly reuse.
Separate argument churn from result work inspect-recomputations
selectOpenTodos(state);
selectOpenTodos(state);
console.log({
results: selectOpenTodos.recomputations(),
dependencies: selectOpenTodos.dependencyRecomputations(),
});Rising dependency counts point to changing arguments or unstable inputs. Rising result counts mean extracted values changed.
Run selector diagnostics repeatedly enable-checks
const selectSummary = createSelector([(state) => state.orders], summarize, {
devModeChecks: { inputStabilityCheck: 'always', identityFunctionCheck: 'always' },
});always adds development work on each call. Return to the default once behavior after diagnosing the selector.
Clear both memoization layers clear-cache
selectOpenTodos.clearCache();
selectOpenTodos.memoizedResultFunc.clearCache();Reselect 5.3 documents both calls. Clearing only the outer cache can leave the result-function cache populated.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| proxy-memoize | npm | proxy-memoize 3.x fits when property-access tracking should decide which nested changes invalidate derived state. |
| memoize-one | npm | Use memoize-one when a general function only needs its most recent argument tuple cached. |
| micro-memoize | npm | Use micro-memoize for configurable function memoization when Redux selector composition and diagnostics are unnecessary. |
More web frontend guides
postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

