GithubHelp home page GithubHelp logo

xsburg / re-reselect Goto Github PK

View Code? Open in Web Editor NEW

This project forked from toomuchdesign/re-reselect

0.0 2.0 0.0 637 KB

Retrieve computed data by querying a dynamically created/memoized collection of reselect selectors.

License: MIT License

JavaScript 40.48% TypeScript 59.52%

re-reselect's Introduction

Re-reselect Build Status

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 a selector is sequentially called with one/few different arguments (example)
  • join similar selectors into one
  • share selectors with props across multiple components (see reselect example and re-reselect solution)
  • instantiate selectors on runtime
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
 */

Table of contents

Installation

npm install reselect
npm install re-reselect

Why? + example

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.

Re-reselect solution

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:

  • Run resolver function and get its result (the cache key)
  • Look for a matching key from the cache
  • Return a cached selector or create a new one if no matching key is found in cache
  • Call selector with provided arguments

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

Other viable solutions

1- Declare a different selector for each different call

Easy but doesn't scale.

2- Declare a makeGetPieceOfData selector factory as explained in Reselect docs

Fine, but has 2 downsides:

  • Bloat your selectors module by exposing both get selectors and makeGet selector factories
  • Two different selector instances given the same arguments will individually recompute and store 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. It's quite verbose (since should be repeated for each selector), that's why re-reselect is here.

FAQ

How do I wrap my existing selector with re-reselect?

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

How do I use multiple inputs to set the cache key?

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

How do I limit the cache size?

Use the cacheObject option.

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?

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

API

Re-reselect exposes its cached selector creator as default export.

import reReselect from 're-reselect';

reReselect([reselect's createSelector arguments])(resolverFunction, { cacheObject, selectorCreator })

Re-reselect accepts your original selector creator arguments and returns a new function which accepts 2 arguments:

  • resolverFunction
  • options { cacheObject, selectorCreator } (optional)

resolverFunction

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.

options.cacheObject

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:

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.

options.selectorCreator

selectorCreator is an optional function describing a custom selectors. By default it uses Reselect's createSelector.

Returns

(Function): a reReselectInstance selector ready to be used like a normal reselect selector.

reReselectInstance(selectorArguments)

Retrieve data for given arguments.

The followings are advanced methods and you won't need them for basic usage!

reReselectInstance.getMatchingSelector(selectorArguments)

Retrieve the selector responding to the given arguments.

reReselectInstance.removeMatchingSelector(selectorArguments)

Remove the selector responding to the given arguments from the cache.

reReselectInstance.clearCache()

Clear the whole reReselectInstance cache.

reReselectInstance.resultFunc

Get resultFunc for easily test composed selectors.

Todo's

  • Flow type definitions?
  • Improve TS tests readability
  • More examples

Contributors

Thanks to you all (emoji key):


Andrea Carraro

๐Ÿ’ป ๐Ÿ“– ๐Ÿš‡ โš ๏ธ ๐Ÿ‘€

Stepan Burguchev

๐Ÿ’ป ๐Ÿ‘€ โš ๏ธ

Mitch Robb

๐Ÿ’ป โš ๏ธ

Stephane Rufer

๐Ÿ’ป โš ๏ธ

Tracy Mullen

๐Ÿ’ป โš ๏ธ

Sushain Cherivirala

๐Ÿ’ป

Steve Mao

๐Ÿ“–

Gaurav Lahoti

๐Ÿ›

Lon

๐Ÿ›

bratushka

๐Ÿ’ป

Anders D. Johnson

๐Ÿ“–

re-reselect's People

Contributors

toomuchdesign avatar xsburg avatar andersdjohnson avatar dante-101 avatar olslash avatar rufman avatar stevemao avatar tracykm avatar

Watchers

 avatar James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.