Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/toomuchdesign/re-reselect
Enhance Reselect selectors with deeper memoization and cache management.
https://github.com/toomuchdesign/re-reselect
memoization reactjs reselect
Last synced: 5 days ago
JSON representation
Enhance Reselect selectors with deeper memoization and cache management.
- Host: GitHub
- URL: https://github.com/toomuchdesign/re-reselect
- Owner: toomuchdesign
- License: mit
- Created: 2017-02-23T13:53:41.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2024-10-28T10:03:14.000Z (6 days ago)
- Last Synced: 2024-10-28T13:18:32.044Z (6 days ago)
- Topics: memoization, reactjs, reselect
- Language: TypeScript
- Homepage:
- Size: 2.94 MB
- Stars: 1,084
- Watchers: 8
- Forks: 46
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.md
Awesome Lists containing this project
- awesome-list - re-reselect
README
# Re-reselect
[![Build status][ci-badge]][ci]
[![Npm version][npm-version-badge]][npm]
[![Npm downloads][npm-downloads-badge]][npm]
[![Test coverage report][coveralls-badge]][coveralls]From [v5](https://github.com/reduxjs/reselect/releases/tag/v5.0.1), `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][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.
![Reselect and re-reselect][reselect-and-re-reselect-sketch]
Useful to:
- **Retain selector's cache** when sequentially **called with one/few different arguments** ([example][example-1])
- **Join similar selectors** into one
- **Share selectors** with props across multiple component instances (see [reselect example][reselect-sharing-selectors] and [re-reselect solution][example-2])
- **Instantiate** selectors **on runtime**
- Enhance `reselect` with [custom caching strategies][cache-objects-docs]```js
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](#installation)
- [Why? + example](#why--example)
- [re-reselect solution](#re-reselect-solution)
- [Other viable solutions](#other-viable-solutions)
- [Examples](#examples)
- [FAQ](#faq)
- [API](#api)
- [`createCachedSelector`](#createCachedSelector)
- [`createStructuredCachedSelector`](#createStructuredCachedSelector)
- [keySelector](#keyselector)
- [options](#options)
- [selector instance][selector-instance-docs]
- [About re-reselect](#about-re-reselect)
- [Todo's](#todos)
- [Contributors](#contributors)## Installation
```console
npm install reselect
npm install re-reselect
```## Why? + example
Let's say `getData` is a `reselect` selector.
```js
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][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`):
```js
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:
1. **Evaluate the `cacheKey`** for the current call by executing `keySelector`
2. **Retrieve** from cache the **`reselect` selector** stored under the given `cacheKey`
3. **Return found selector or create a new one** if no selector was found
4. **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][example-1].
#### 2- Declare a `makeGetPieceOfData` selector factory as explained in Reselect docs
The solution suggested in [Reselect docs][reselect-sharing-selectors] is fine, but it has a few downsides:
- Bloats your code by exposing both `get` selectors and `makeGet` selector 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](https://github.com/reactjs/reselect/pull/213))#### 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][example-1]
- [Avoid selector factories][example-2]
- [Cache API calls][example-3]
- [Programmatic keySelector composition][example-4]
- [Usage with Selectorator][example-5]## FAQ
How do I wrap my existing selector with re-reselect?
Given your `reselect` selectors:
```js
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:
```js
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!
```js
const myData = getMyData(state, 'foo', 'bar');
```
How do I use multiple inputs to set the cacheKey?
A few good examples and [a bonus](https://github.com/toomuchdesign/re-reselect/issues/3):
```js
// 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`][cache-objects-docs] which provides that feature by supplying a [`cacheObject` option](#cacheobject).
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][example-2] shows how `re-reselect` would solve the scenario described in [reselect docs][reselect-sharing-selectors].
How do I test a re-reselect selector?
Like a normal reselect selector!
`re-reselect` selectors expose the same `reselect` testing methods:
- `dependencies`
- `resultFunc`
- `recomputations`
- `resetRecomputations`Read more about testing selectors on [`reselect` docs][reselect-test-selectors].
#### 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][reselect-selectors-methods].
```js
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
### createCachedSelector
```js
import {createCachedSelector} from 're-reselect';createCachedSelector(
// ...reselect's `createSelector` arguments
)(
keySelector | { options }
)
```Takes the same arguments as reselect's [`createSelector`][reselect-create-selector] and returns a new function which accepts a [`keySelector`](#keyselector) or an [`options`](#options) object.
**Returns** a [selector instance][selector-instance-docs].
### createStructuredCachedSelector
```js
import {createStructuredCachedSelector} from 're-reselect';createStructuredCachedSelector(
// ...reselect's `createStructuredSelector` arguments
)(
keySelector | { options }
)
```Takes the same arguments as reselect's [`createStructuredSelector`][reselect-create-structured-selector] and returns a new function which accepts a [`keySelector`](#keyselector) or an [`options`](#options) object.
**Returns** a [selector instance][selector-instance-docs].
### 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](#optionscacheobject)).
The `keySelector` idea comes from [Lodash's .memoize resolver][lodash-memoize].
### options
#### keySelector
Type: `function`
Default: `undefined`The [`keySelector`](#keyselector) used by the cached selector.
#### cacheObject
Type: `object`
Default: [`FlatObjectCache`][cache-objects-docs]An optional custom **cache strategy object** to handle the caching behaviour. Read more about [re-reselect's custom cache here][cache-objects-docs].
#### keySelectorCreator
Type: `function`
Default: `undefined`An optional function with the following signature returning the [`keySelector`](#keyselector) used by the cached selector.
```typescript
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**](https://github.com/toomuchdesign/re-reselect/pull/73). It overrides any provided `keySelector`.
See [programmatic keySelector composition][example-4] example.
#### selectorCreator
Type: `function`
Default: `reselect`'s [`createSelector`][reselect-create-selector]An optional function describing a [custom version of createSelector][reselect-create-selector-creator].
### re-reselect selector instance
`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!
#### selector`.getMatchingSelector(selectorArguments)`
Retrieve the selector responding to the given arguments.
#### 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](https://github.com/toomuchdesign/re-reselect/issues/40)).
#### selector`.clearCache()`
Clear whole `selector` cache.
#### selector`.dependencies`
Get an array containing the provided `inputSelectors`. Refer to relevant discussion on [Reselect repo][reselect-test-selectors-dependencies].
#### selector`.resultFunc`
Get `resultFunc` for easily [testing composed selectors][reselect-test-selectors].
#### selector`.recomputations()`
Return the number of times the selector's result function has been recomputed.
#### selector`.resetRecomputations()`
Reset `recomputations` count.
#### selector`.keySelector`
Get `keySelector` for utility compositions or testing.
## About re-reselect
- [re-reselect your whole redux state](https://patrickdesjardins.com/blog/re-reselect-your-whole-redux-state)
- [Understanding reselect and re-reselect](http://alexnitta.com/understanding-reselect-and-re-reselect/)
- [React re-reselect: Better memoization and cache management](https://blog.logrocket.com/react-re-reselect-better-memoization-cache-management/)
- [Advanced Redux patterns: selectors](https://blog.brainsandbeards.com/advanced-redux-patterns-selectors-cb9f88381d74)
- [Be selective with your state](https://medium.com/riipen-engineering/be-selective-with-your-state-8f1be76cb9f4)
- [A swift developerβs React Native experience](https://swiftwithjustin.co/2018/06/24/a-swift-developers-react-native-experience)
- [5 key Redux libraries to improve code reuse](https://blog.logrocket.com/5-redux-libraries-to-improve-code-reuse-9f93eaceaa83)
- [Rematch's docs](https://github.com/rematch/rematch/blob/1.1.0/plugins/select/README.md#re-reselect)
- [Redux re-reselect playground](https://codesandbox.io/s/135rwqj2jj)## Todo's
- Improve tests readability
- Port to native TS based on reselect v5 approach
- Find out whether `re-reselect` should be deprecated in favour of `reselect` memoization/cache options## Contributors
Thanks to you all ([emoji key][docs-all-contributors]):
Andrea Carraro
π» π π β οΈ π
Stepan Burguchev
π» π€ π¬ π β οΈ
Sergei Grishchenko
π» π€ β οΈ π§
Mateusz BurzyΕski
π» π
Mitch Robb
π» β οΈ
Stephane Rufer
π» β οΈ
Tracy Mullen
π» β οΈ
Sushain Cherivirala
π»
Steve Mao
π
Gaurav Lahoti
π
Lon
π
bratushka
π»
Anders D. Johnson
π
JΓΊlius Retzer
π
Maarten Schumacher
π€
Alexander Jarvis
π€
Gregg B
π‘
Ian Obermiller
π
Kanitkorn Sujautra
π
Brian Kraus
π
el-dav
π
Augustin Riedinger
π€
RichardForrester
π€
Alfonso Millan
π
parkerault
π
johannes
π
Alex de la Mare
π π»
Alex Khizhnyi
π»
[reselect]: https://github.com/reactjs/reselect
[reselect-sharing-selectors]: https://github.com/reduxjs/reselect/tree/v4.0.0#sharing-selectors-with-props-across-multiple-component-instances
[reselect-test-selectors]: https://github.com/reactjs/reselect/tree/v4.0.0#q-how-do-i-test-a-selector
[reselect-test-selectors-dependencies]: https://github.com/reduxjs/reselect/issues/76#issuecomment-299194186
[reselect-selectors-methods]: https://github.com/reduxjs/reselect/blob/v4.0.0/src/index.js#L81
[reselect-create-selector]: https://github.com/reactjs/reselect/tree/v4.0.0#createselectorinputselectors--inputselectors-resultfunc
[reselect-create-structured-selector]: https://github.com/reduxjs/reselect/tree/v4.0.0#createstructuredselectorinputselectors-selectorcreator--createselector
[reselect-create-selector-creator]: https://github.com/reactjs/reselect/tree/v4.0.0#createselectorcreatormemoize-memoizeoptions
[lodash-memoize]: https://lodash.com/docs/4.17.4#memoize
[ci-badge]: https://github.com/toomuchdesign/re-reselect/actions/workflows/ci.yml/badge.svg
[ci]: https://github.com/toomuchdesign/re-reselect/actions/workflows/ci.yml
[coveralls-badge]: https://coveralls.io/repos/github/toomuchdesign/re-reselect/badge.svg?branch=master
[coveralls]: https://coveralls.io/github/toomuchdesign/re-reselect?branch=master
[npm]: https://www.npmjs.com/package/re-reselect
[npm-version-badge]: https://img.shields.io/npm/v/re-reselect.svg
[npm-downloads-badge]: https://img.shields.io/npm/dm/re-reselect.svg
[reselect-and-re-reselect-sketch]: examples/reselect-and-re-reselect.png?raw=true
[example-1]: examples/1-join-selectors.md
[example-2]: examples/2-avoid-selector-factories.md
[example-3]: examples/3-cache-api-calls.md
[example-4]: examples/4-programmatic-keyselector-composition.md
[example-5]: examples/5-selectorator.md
[selector-instance-docs]: #re-reselect-selector-instance
[cache-objects-docs]: src/cache#readme
[docs-all-contributors]: https://allcontributors.org/docs/en/emoji-key