Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
re-reselect
Advanced tools
Memoize selectors and avoid recalculation between calls with different inputs
The re-reselect npm package is an extension of the reselect library, which is used for creating memoized selectors in Redux. re-reselect adds the ability to cache selectors based on dynamic keys, which can help optimize performance in applications with complex state trees.
Dynamic Key-Based Caching
This feature allows you to create selectors that cache their results based on dynamic keys. In this example, the selector caches results based on the item ID, which can improve performance by avoiding unnecessary recalculations.
const createCachedSelector = require('re-reselect').default;
const inputSelector = state => state.items;
const idSelector = (state, id) => id;
const cachedSelector = createCachedSelector(
inputSelector,
idSelector,
(items, id) => items.find(item => item.id === id)
)(idSelector);
const state = { items: [{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }] };
console.log(cachedSelector(state, 1)); // { id: 1, name: 'Item 1' }
Custom Cache Management
This feature allows you to use custom cache management strategies. In this example, an LRU (Least Recently Used) cache is used to manage the cached selectors, which can help in scenarios where memory usage needs to be controlled.
const createCachedSelector = require('re-reselect').default;
const LRU = require('lru-cache');
const cache = new LRU({ max: 100 });
const inputSelector = state => state.items;
const idSelector = (state, id) => id;
const cachedSelector = createCachedSelector(
inputSelector,
idSelector,
(items, id) => items.find(item => item.id === id)
)({ cacheObject: cache });
const state = { items: [{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }] };
console.log(cachedSelector(state, 1)); // { id: 1, name: 'Item 1' }
Composite Key Selectors
This feature allows you to create selectors that use composite keys for caching. In this example, the selector caches results based on a combination of item ID and type, which can be useful for more complex state structures.
const createCachedSelector = require('re-reselect').default;
const inputSelector = state => state.items;
const compositeKeySelector = (state, id, type) => `${id}-${type}`;
const cachedSelector = createCachedSelector(
inputSelector,
compositeKeySelector,
(items, key) => items.find(item => `${item.id}-${item.type}` === key)
)(compositeKeySelector);
const state = { items: [{ id: 1, type: 'A', name: 'Item 1' }, { id: 2, type: 'B', name: 'Item 2' }] };
console.log(cachedSelector(state, 1, 'A')); // { id: 1, type: 'A', name: 'Item 1' }
Reselect is a simple library for creating memoized selectors in Redux. It does not support dynamic key-based caching like re-reselect, but it is a foundational library that re-reselect builds upon. Reselect is useful for creating selectors that are memoized based on their input arguments.
Memoize-one is a library that memoizes the most recent result of a function. It is simpler than re-reselect and does not support dynamic key-based caching. It is useful for scenarios where you only need to cache the last result of a function call.
Re-reselect is an extension of reselect that adds dynamic key-based caching. It is more powerful than reselect and memoize-one for scenarios where you need to cache results based on dynamic keys.
Improve Reselect selectors performance/usage on a few edge cases, by initializing selectors on runtime, using a memoized factory.
Re-reselect returns a reselect-like selector, which is able to determine internally when querying a new selector instance or a cached one on the fly, depending on the supplied arguments.
Useful to reduce selectors recalculation when:
...or to:
import reselect from 'reselect';
import createCachedSelector from 're-reselect';
const selectorA = state => state.a;
const selectorB = state => state.b;
const cachedSelector = createCachedSelector(
// Set up your Reselect selector as normal:
// reselect inputSelectors:
selectorA,
selectorB,
(state, someArg) => someArg,
// reselect resultFunc:
(A, B, someArg) => expensiveComputation(A, B, someArg),
)(
/*
* Now it comes the re-reselect caching part:
* declare a resolver function, used as mapping cache key.
* It takes the same arguments as the generated selector
* and must return a string or number (the cache key).
*
* A new selector will be cached for each different returned key
*
* In this example the second argument of the selector is used as cache key
*/
(state, someArg) => someArg,
);
// Now you are ready to call/expose the cached selector like a normal selector:
/*
* Call selector with "foo" and with "bar":
* 2 different selectors are created, called and cached behind the scenes.
* The selectors return their computed result.
*/
const fooResult = cachedSelector(state, 'foo');
const barResult = cachedSelector(state, 'bar');
/*
* Call selector with "foo" again:
* "foo" hits the cache, now: the selector cached under "foo" key
* is retrieved, called again and the result is returned.
*/
const fooResultAgain = cachedSelector(state, 'foo');
/*
* Note that fooResult === fooResultAgain.
* The cache was not invalidated by "cachedSelector(state, 'bar')" call
*/
npm install reselect
npm install re-reselect
I found my self wrapping a library of data elaboration (quite heavy stuff) with reselect selectors (getPieceOfData
in the example).
On each store update, I had to repeatedly call the selector in order to retrieve all the pieces of data needed by my UI. Like this:
getPieceOfData(state, itemId, 'dataA', otherArg);
getPieceOfData(state, itemId, 'dataB', otherArg);
getPieceOfData(state, itemId, 'dataC', otherArg);
What happens, here? getPieceOfData
selector cache is invalidated on each call because of the changing 3rd dataX
argument.
createCachedSelector
keeps a private collection of selectors and store them by key
.
key
is the output of the resolver
function, declared at selector initialization.
resolver
is a custom function which receives the same arguments as the final selector (in the example: state
, itemId
, 'dataX'
, otherArgs
) and returns a string
or number
.
That said, I was able to configure re-reselect
to retrieve my data by querying a set of cached selectors using the 3rd argument as cache key:
const getPieceOfData = createCachedSelector(
state => state,
(state, itemId) => itemId
(state, itemId, dataType) => dataType
(state, itemId, dataType, otherArg) => otherArg
(state, itemId, dataType, otherArg) => expensiveComputation(state, itemId, dataType, otherArg),
)(
(state, itemId, dataType) => dataType, // Memoize by dataType
);
The final result is a normal selector taking the same arguments as before.
But now, each time the selector is called, the following happens behind the scenes:
resolver
function and get its result (the cache key)Re-reselect stays completely optional and uses your installed reselect library under the hoods (reselect is declared as a peer dependency).
Furthermore you can use any custom selector (see API).
Easy but doesn't scale.
makeGetPieceOfData
selector factory as explained in Reselect docsFine, but has 2 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. It's quite verbose (since should be repeated for each selector), that's why re-reselect is here.
Given your reselect
selectors:
import { createSelector } from 'reselect';
export const getMyData = createSelector(
selectorA, // eg: selectors receive: (state, arg1, arg2)
selectorB,
selectorC,
(A, B, C) => doSomethingWith(A, B, C),
);
...it becomes:
import createCachedSelector from 're-reselect';
export const getMyData = createCachedSelector(
selectorA, // eg: selectors receive: (state, arg1, arg2)
selectorB,
selectorC,
(A, B, C) => doSomethingWith(A, B, C),
)(
(state, arg1, arg2) => arg2, // Use arg2 as cache key
);
Voilà, getMyData
is ready for use!
let myData = getMyData(state, 'foo', 'bar');
The cache key is defined by the output of the resolverFunction
.
resolverFunction
is a function which receives the same arguments of your inputSelectors
and must return a string or number.
A few good examples and a bonus:
// Basic usage: use a single argument as cache key
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 the cacheObject
option.
This example is how re-reselect
would solve the scenario described in Reselect docs.
We can directly declare getVisibleTodos
selector. Since re-reselect
handles selectors instantiation transparently, there is no need to declare a makeGetVisibleTodos
factory.
selectors/todoSelectors.js
import createCachedSelector from 're-reselect';
const getVisibilityFilter = (state, props) =>
state.todoLists[props.listId].visibilityFilter
const getTodos = (state, props) =>
state.todoLists[props.listId].todos
const getVisibleTodos = createCachedSelector(
[ getVisibilityFilter, getTodos ],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_COMPLETED':
return todos.filter(todo => todo.completed)
case 'SHOW_ACTIVE':
return todos.filter(todo => !todo.completed)
default:
return todos
}
}
)(
/*
* Re-reselect resolver function.
* Cache/call a new selector for each different "listId"
*/
(state, props) => props.listId,
);
export default getVisibleTodos;
containers/VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { getVisibleTodos } from '../selectors'
// No need of makeMapStateToProps function:
// use getVisibleTodos as a normal selector
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props)
}
}
// ...
Just like a normal reselect selector! Read more here.
Each re-reselect cached 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 like:
resultFunc
recomputations
resetRecomputations
import createCachedSelector from 're-reselect';
export const getMyData = createCachedSelector(
selectorA,
selectorB,
(A, B) => doSomethingWith(A, B),
)(
(state, arg1) => arg1, // Use arg1 as cache key
);
// ...
// Call the selector to retrieve data
const myFooData = getMyData(state, 'foo');
const myBarData = getMyData(state, 'bar');
// Call getMatchingSelector to retrieve the 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();
Re-reselect
exposes its cached selector creator as default export.
import reReselect from 're-reselect';
Re-reselect accepts your original selector creator arguments and returns a new function which accepts 2 arguments:
resolverFunction
options { cacheObject, selectorCreator }
(optional)resolverFunction
is a function which receives the same arguments of your selectors (and inputSelectors
) and must return a string or number. The result is used as cache key to store/retrieve selector instances.
Cache keys of type number
are treated like strings, since they are assigned to a JS object as arguments.
The resolver idea is inspired by Lodash's .memoize util.
An optional custom strategy object to handle the caching behaviour. It must adhere to the following interface:
interface ICacheObject {
set (key: string|number, selectorFn: Function): void;
get (key: string|number): Function;
remove (key: string|number): void;
clear (): void;
}
re-reselect
provides 3 ready to use cache object creators:
FlatCacheObject
(default)FifoCacheObject
(first in first out cache)LruCacheObject
(least recently used cache)import createCachedSelector, { LruCacheObject, FifoCacheObject } from re-reselect;
createCachedSelector(
// ...
)(
resolverFunction,
{
cacheObject: new LruCacheObject({ cacheSize: 5 });
// or:
// cacheObject: new FifoCacheObject({ cacheSize: 5 });
}
)
The default cache strategy, FlatCache
doesn't limit cache.
You can provide any kind of caching strategy. Just write your own. You can use the existing ones as starting point.
selectorCreator
is an optional function describing a custom selectors. By default it uses Reselect's createSelector
.
(Function): a reReselectInstance
selector ready to be used like a normal reselect selector.
Retrieve data for given arguments.
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 the selector responding to the given arguments from the cache.
.clearCache()
Clear the whole reReselectInstance
cache.
.resultFunc
Get resultFunc
for easily test composed selectors.
Thanks to you all (emoji key):
Andrea Carraro 💻 📖 🚇 ⚠️ 👀 | Stepan Burguchev 💻 👀 ⚠️ | Mitch Robb 💻 ⚠️ | Stephane Rufer 💻 ⚠️ | Tracy Mullen 💻 ⚠️ | Sushain Cherivirala 💻 | Steve Mao 📖 |
---|---|---|---|---|---|---|
Gaurav Lahoti 🐛 | Lon 🐛 | bratushka 💻 |
1.0.0
selectorCreator
argument is deprecated in favour of an option objectcacheObject
and selectorCreator
optionsRollup.js
as bundlerFAQs
Enhance Reselect selectors with deeper memoization and cache management
The npm package re-reselect receives a total of 112,840 weekly downloads. As such, re-reselect popularity was classified as popular.
We found that re-reselect demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.