re-reselect
    TypeScript icon, indicating that this package has built-in type declarations

    4.0.0 • Public • Published

    Re-reselect

    Build status Npm version Npm downloads Test coverage report

    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.

    Reselect and re-reselect

    Useful to:

    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 -S
    npm install re-reselect -S

    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 getPieceOfData = 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.

    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 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)

    3- Wrap your makeGetPieceOfData selector factory into a memoizer function and call the returning memoized selector

    This is what re-reselect actually does. 😀

    Examples

    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:

    • dependencies
    • resultFunc
    • recomputations
    • resetRecomputations

    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

    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.

    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.

    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

    keySelector

    Type: function
    Default: undefined

    The keySelector used by the cached selector.

    cacheObject

    Type: object
    Default: FlatObjectCache

    An optional custom cache strategy object to handle the caching behaviour. Read more about re-reselect's custom cache here.

    keySelectorCreator

    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.

    selectorCreator

    Type: function
    Default: reselect's createSelector

    An optional function describing a custom version of createSelector.

    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).

    selector.clearCache()

    Clear whole selector cache.

    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.

    selector.keySelector

    Get keySelector for utility compositions or testing.

    About re-reselect

    Todo's

    • Improve TS tests readability
    • More examples

    Contributors

    Thanks to you all (emoji key):


    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

    🐛

    Install

    npm i re-reselect

    DownloadsWeekly Downloads

    81,186

    Version

    4.0.0

    License

    MIT

    Unpacked Size

    203 kB

    Total Files

    23

    Last publish

    Collaborators

    • toomuchdesign