toomuchdesign/re-reselect
toomuchdesign/re-reselect has 1,092 GitHub stars and 46 forks. GitHot measured 0 stars in 24 hours. Last checked 2026-11-09 10:27 UTC. Last push 2026-11-02.
Enhance Reselect selectors with deeper memoization and cache management.
Measured star growth
- in 24h
- 0
- in 7 days
- Update delayed
- in 15 days
- Update delayed
- in 1 month
- Update delayed
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.md
OriginalRe-reselect
From v5, reselect provides the ability to natively implement custom memoization/caching solutions via createSelector options. Most of the features re-reselect used to enable should be now natively available in reselect. re-reselect will try to support reselect v5+ for backward compatibility reasons.
re-reselect is a lightweight wrapper around Reselect meant to enhance selectors with deeper memoization and cache management.
Switching between different arguments using standard reselect selectors causes cache invalidation since default reselect cache has a limit of one.
re-reselect forwards different calls to different reselect selectors stored in cache, so that computed/memoized values are retained.
re-reselect selectors work as normal reselect selectors but they are able to determine when creating a new selector or querying a cached one on the fly, depending on the supplied arguments.

Useful to:
- Retain selector's cache when sequentially called with one/few different arguments (example)
- Join similar selectors into one
- Share selectors with props across multiple component instances (see reselect example and re-reselect solution)
- Instantiate selectors on runtime
- Enhance
reselectwith custom caching strategies
import {createCachedSelector} from 're-reselect';
// Normal reselect routine: declare "inputSelectors" and "resultFunc"
const getUsers = state => state.users;
const getLibraryId = (state, libraryName) => state.libraries[libraryName].id;
const getUsersByLibrary = createCachedSelector(
// inputSelectors
getUsers,
getLibraryId,
// resultFunc
(users, libraryId) => expensiveComputation(users, libraryId),
)(
// re-reselect keySelector (receives selectors' arguments)
// Use "libraryName" as cacheKey
(_state_, libraryName) => libraryName
);
// Cached selectors behave like normal selectors:
// 2 reselect selectors are created, called and cached
const reactUsers = getUsersByLibrary(state, 'react');
const vueUsers = getUsersByLibrary(state, 'vue');
// This 3rd call hits the cache
const reactUsersAgain = getUsersByLibrary(state, 'react');
// reactUsers === reactUsersAgain
// "expensiveComputation" called twice in total
Table of contents
Installation
npm install reselect
npm install re-reselect
Why? + example
Let's say getData is a reselect selector.
getData(state, itemId, 'dataA');
getData(state, itemId, 'dataB');
getData(state, itemId, 'dataA');
The 3rd argument invalidates reselect cache on each call, forcing getData to re-evaluate and return a new value.
re-reselect solution
re-reselect selectors keep a cache of reselect selectors stored by cacheKey.
cacheKey is the return value of the keySelector function. It's by default a string or number but it can be anything depending on the chosen cache strategy (see cache objects docs).
keySelector is a custom function which:
- takes the same arguments as the selector itself (in the example:
state,itemId,dataType) - returns a
cacheKey
A unique persisting reselect selector instance stored in cache is used to compute data for a given cacheKey (1:1).
Back to the example, we might setup re-reselect to retrieve data by querying one of the cached selectors using the 3rd argument as cacheKey, allowing cache invalidation only when state or itemId change (but not dataType):
const getData = createCachedSelector(
state => state,
(state, itemId) => itemId,
(state, itemId, dataType) => dataType,
(state, itemId, dataType) => expensiveComputation(state, itemId, dataType)
)(
(state, itemId, dataType) => dataType // Use dataType as cacheKey
);
Replacing a selector with a cached selector is invisible to the consuming application since the API is the same.
When a cached selector is called, the following happens behind the scenes:
- Evaluate the
cacheKeyfor the current call by executingkeySelector - Retrieve from cache the
reselectselector stored under the givencacheKey - Return found selector or create a new one if no selector was found
- Call returned selector with provided arguments
Other viable solutions
1- Declare a different selector for each different call
Easy, but doesn't scale. See "join similar selectors" example.
2- Declare a makeGetPieceOfData selector factory as explained in Reselect docs
The solution suggested in Reselect docs is fine, but it has a few downsides:
- Bloats your code by exposing both
getselectors andmakeGetselector factories - Needs to import/call the selector factory instead of directly using the selector
- Two different instances, given the same arguments, will individually store and recompute the same result (read this)
3- Wrap your makeGetPieceOfData selector factory into a memoizer function and call the returning memoized selector
This is what re-reselect actually does. 😀
Examples
- Join similar selectors
- Avoid selector factories
- Cache API calls
- Programmatic keySelector composition
- Usage with Selectorator
FAQ
How do I wrap my existing selector with re-reselect?
Given your reselect selectors:
import { createSelector } from 'reselect';
export const getMyData = createSelector(
selectorA,
selectorB,
selectorC,
(A, B, C) => doSomethingWith(A, B, C),
);
...add keySelector in the second function call:
import { createCachedSelector } from 're-reselect';
export const getMyData = createCachedSelector(
selectorA,
selectorB,
selectorC,
(A, B, C) => doSomethingWith(A, B, C),
)(
(state, arg1, arg2) => arg2, // Use arg2 as cacheKey
);
Voilà, getMyData is ready for use!
const myData = getMyData(state, 'foo', 'bar');
How do I use multiple inputs to set the cacheKey?
A few good examples and a bonus:
// Basic usage: use a single argument as cacheKey
createCachedSelector(
// ...
)(
(state, arg1, arg2, arg3) => arg3
)
// Use multiple arguments and chain them into a string
createCachedSelector(
// ...
)(
(state, arg1, arg2, arg3) => `${arg1}:${arg3}`
)
// Extract properties from an object
createCachedSelector(
// ...
)(
(state, props) => `${props.a}:${props.b}`
)
How do I limit the cache size?
Use a cacheObject which provides that feature by supplying a cacheObject option.
You can also write your own cache strategy!
How to share a selector across multiple components while passing in props and retaining memoization?
This example shows how re-reselect would solve the scenario described in reselect docs.
How do I test a re-reselect selector?
Like a normal reselect selector!
re-reselect selectors expose the same reselect testing methods:
dependenciesresultFuncrecomputationsresetRecomputations
Read more about testing selectors on reselect docs.
Testing reselect selectors stored in the cache
Each re-reselect selector exposes a getMatchingSelector method which returns the underlying matching selector instance for the given arguments, instead of the result.
getMatchingSelector expects the same arguments as a normal selector call BUT returns the instance of the cached selector itself.
Once you get a selector instance you can call its public methods.
import {createCachedSelector} from 're-reselect';
export const getMyData = createCachedSelector(selectorA, selectorB, (A, B) =>
doSomethingWith(A, B)
)(
(state, arg1) => arg1 // cacheKey
);
// Call your selector
const myFooData = getMyData(state, 'foo');
const myBarData = getMyData(state, 'bar');
// Call getMatchingSelector method to retrieve underlying reselect selectors
// which generated "myFooData" and "myBarData" results
const myFooDataSelector = getMyData.getMatchingSelector(state, 'foo');
const myBarDataSelector = getMyData.getMatchingSelector(state, 'bar');
// Call reselect's selectors methods
myFooDataSelector.recomputations();
myFooDataSelector.resetRecomputations();
API
Functions
createCachedSelector
import {createCachedSelector} from 're-reselect';
createCachedSelector(
// ...reselect's `createSelector` arguments
)(
keySelector | { options }
)
Takes the same arguments as reselect's createSelector and returns a new function which accepts a keySelector or an options object.
Returns a selector instance.
createCachedSelector.withTypes<State>()
Creates a pre-typed version of createCachedSelector with the state type baked in, mirroring reselect's createSelector.withTypes. Set the state type once instead of annotating it on every input selector:
const createAppCachedSelector = createCachedSelector.withTypes<RootState>();
Without withTypes you annotate state on every input selector:
const selectTodoById = createCachedSelector(
(state: RootState) => state.todos,
(state: RootState, id: string) => id,
(todos, id) => todos[id],
)((state: RootState, id) => id);
With a pre-typed creator, state is inferred everywhere while per-selector params and the result stay inferred:
const selectTodoById = createAppCachedSelector(
[
(state) => state.todos, // `state` inferred as `RootState`
(state, id: string) => id, // `id` param still inferred
],
(todos, id) => todos[id],
)((state, id) => id); // `keySelector` `state` inferred too
Pass input selectors as an array. As with reselect's
createSelector.withTypes, state inference only works when input selectors are given as a single array argument. With the variadic form (input selectors spread as separate arguments),stateand the combiner arguments are inferred asany.
Only the state type is pre-typed. withTypes is a type-only helper: at runtime it returns the same createCachedSelector unchanged. Calls can be chained to narrow the state further (the override type must extend the current one).
createStructuredCachedSelector
import {createStructuredCachedSelector} from 're-reselect';
createStructuredCachedSelector(
// ...reselect's `createStructuredSelector` arguments
)(
keySelector | { options }
)
Takes the same arguments as reselect's createStructuredSelector and returns a new function which accepts a keySelector or an options object.
Returns a selector instance.
Arguments
keySelector
A custom function receiving the same arguments as your selectors (and inputSelectors) and returning a cacheKey.
cacheKey is by default a string or number but can be anything depending on the chosen cache strategy (see cacheObject option).
The keySelector idea comes from Lodash's .memoize resolver.
options
| Option | Type | Default | Description |
|---|---|---|---|
keySelector |
function |
undefined |
The keySelector used by the cached selector. |
cacheObject |
object |
FlatObjectCache |
An optional custom cache strategy object to handle the caching behaviour. Read more about re-reselect's custom cache here. |
keySelectorCreator |
function |
undefined |
An optional function returning the keySelector used by the cached selector. Overrides any provided keySelector. |
selectorCreator |
function |
reselect's createSelector |
An optional function describing a custom version of createSelector. |
options.keySelectorCreator
type keySelectorCreator = (selectorInputs: {
inputSelectors: InputSelector[];
resultFunc: ResultFunc;
// Absent unless a `keySelector` was also supplied
keySelector?: KeySelector;
}) => KeySelector;
This allows the ability to dynamically generate keySelectors on runtime based on provided inputSelectors/resultFunc supporting key selectors composition. It overrides any provided keySelector.
See programmatic keySelector composition example.
Returned selector
createCachedSelector and createStructuredCachedSelector return a selector instance which extends the API of a standard reselect selector.
The following are advanced methods and you won't need them for basic usage!
Cache
| Member | Description |
|---|---|
selector.getMatchingSelector(selectorArguments) |
Retrieve the selector responding to the given arguments, or undefined if none is cached. |
selector.removeMatchingSelector(selectorArguments) |
Remove from the cache the selector responding to the given arguments. |
selector.cache |
Get the cacheObject instance being used by the selector (for advanced caching operations like this). |
selector.clearCache() |
Clear whole selector cache. |
selector.keySelector |
Get keySelector for utility compositions or testing. |
Inherited from reselect
| Member | Description |
|---|---|
selector.dependencies |
Get an array containing the provided inputSelectors. Refer to relevant discussion on Reselect repo. |
selector.resultFunc |
Get resultFunc for easily testing composed selectors. |
selector.recomputations() |
Return the number of times the selector's result function has been recomputed. |
selector.resetRecomputations() |
Reset recomputations count. |
Migrating from v5
v6 is a rewrite in TypeScript. JavaScript users only need the runtime changes. The changelog has the full migration table.
Runtime and packaging:
resetRecomputations()returnsundefined. Read the count withrecomputations().- A missing
keySelectorthrows when the selector is created, not on its first call. - Any falsy value returned by a custom
cacheObject.get()counts as a cache miss. - The UMD bundle is gone: use an ESM CDN such as
https://esm.sh/re-reselect, or bundle re-reselect. Deep imports likere-reselect/dist/...no longer resolve. - In native Node ESM,
import reReselect from 're-reselect'fails: use named imports orimport * as. - The bundles use ES2020 syntax (ES2019 for the
moduleentry). Transpilenode_modulesif you target older engines.
TypeScript:
- Explicit type arguments like
createCachedSelector<S, R1, T>(…)fail withTS2558. Drop them and annotate the input selectors, or pass<[typeof selectA, typeof selectB], Result>. - Selector arguments are inferred from the input selectors. Extra or missing arguments are rejected, and so is a
keySelectorreading an argument no input selector declares. Add a parametric input selector such as(state, id) => idfor that argument. - An inline input selector without a
stateannotation infersstateasany, as in reselect v5. Annotate it, or usewithTypeswith an array of input selectors. getMatchingSelectoris typed as returningOutputSelector | undefined, as it already did at runtime on a cache miss. Check the result or use?..keySelectorCreatorreceiveskeySelectoras optional. Check it before calling it.SelectorandOutputSelectorare no longer exported: import them fromreselect.CreateSelectorInstanceis gone too: use reselect'sCreateSelectorFunction. TheParametric*types are gone: writeParametricSelector<S, P, R>asSelector<S, R, [P]>.OutputCachedSelector<InputSelectors, Result>now types the cached selector, not the factory returned bycreateCachedSelector(...). Annotate the selector instead.KeySelectorCreator<InputSelectors, Result>takes the input selectors instead of the state type.- A
createStructuredCachedSelectorcall with a very large selectors object (roughly 50+ keys) can fail withTS2589. Split it into smaller ones.
About re-reselect
Explore related projects
codecrafters-io/build-your-own-x
Master programming by recreating your favorite technologies from scratch.
561,398 starspublic-apis/public-apis
A collective list of free APIs
497,679 starsmorluto/rea
Reverse engineer anything with agents, from app behavior down to native binaries.
447,495 starsopenclaw/openclaw
The AI that really does things. Any OS. Any Platform. The lobster way. 🦞
394,261 stars