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:
reselect with custom
caching strategiesimport {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 totalnpm install reselect -S
npm install re-reselect -S
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 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:
state, itemId, dataType)cacheKeyA 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:
cacheKey for the current
call by executing keySelectorreselect selector stored under the given
cacheKeyEasy, but doesn’t scale. See “join similar selectors” example.
makeGetPieceOfData selector factory as explained
in Reselect docsThe solution suggested in Reselect docs is fine, but it has a few downsides:
get selectors and
makeGet selector factoriesmakeGetPieceOfData selector factory into a
memoizer function and call the returning memoized selectorThis is what re-reselect actually does. 😀
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');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}`
)Use a cacheObject which
provides that feature by supplying a cacheObject option.
You can also write your own cache strategy!
This example
shows how re-reselect would solve the scenario described in
reselect
docs.
Like a normal reselect selector!
re-reselect selectors expose the same
reselect testing methods:
dependenciesresultFuncrecomputationsresetRecomputationsRead more about testing selectors on reselect
docs.
reselect selectors stored in the cacheEach 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();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.
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.
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.
Type: function
Default: undefined
The keySelector used by the
cached selector.
Type: object
Default: FlatObjectCache
An optional custom cache strategy object to handle the caching behaviour. Read more about re-reselect’s custom cache here.
Type: function
Default: undefined
An optional function with the following signature returning the keySelector used by the cached
selector.
type keySelectorCreator = (selectorInputs: {
inputSelectors: InputSelector[];
resultFunc: ResultFunc;
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.
Type: function
Default: reselect’s createSelector
An optional function describing a custom version of createSelector.
createCachedSelector and
createStructuredCachedSelector return a selector
instance which extends the API of a standard reselect
selector.
The followings are advanced methods and you won’t need them for basic usage!
.getMatchingSelector(selectorArguments)Retrieve the selector responding to the given arguments.
.removeMatchingSelector(selectorArguments)Remove from the cache the selector responding to the given arguments.
.cacheGet the cacheObject instance being used by the selector (for advanced caching operations like this).
.clearCache()Clear whole selector cache.
.dependenciesGet an array containing the provided inputSelectors.
Refer to relevant discussion on Reselect
repo.
.resultFuncGet resultFunc for easily testing
composed selectors.
.recomputations()Return the number of times the selector’s result function has been recomputed.
.resetRecomputations()Reset recomputations count.
.keySelectorGet keySelector for utility compositions or testing.
Thanks to you all (emoji key):