Options
All
  • Public
  • Public/Protected
  • All
Menu

memosel

memosel

A library for creating memoized "selector" functions

Installation

with NPM

npm i rtkex --save

with YARN

yarn add rtkex

Features

  • Fully Typescript supported
  • Multiple keys caching
  • Time To Live caching supported
  • Structured selector supported

Recipes

Basic Usage

import memosel from "memosel";

const selectShopItems = (state) => state.shop.items;
const selectTaxPercent = (state) => state.shop.taxPercent;

const selectSubtotal = memosel()
// use "input" selector and map it return value to "items" prop of selected value
.use("items", selectShopItems)
.build((selected) =>
selected.items.reduce((subtotal, item) => subtotal + item.value, 0)
);

const selectTax = memosel()
.use("subtotal", selectSubtotal)
.use("taxPercent", selectTaxPercent)
.build((selected) => selected.subtotal * (selected.taxPercent / 100));

const selectTotal = memosel()
.use("subttoal", selectSubtotal)
.use("tax", selectTax)
.build((selected) => ({ total: selected.subtotal + seleted.tax }));

const exampleState = {
shop: {
taxPercent: 8,
items: [
{ name: "apple", value: 1.2 },
{ name: "orange", value: 0.95 },
],
},
};

console.log(selectSubtotal(exampleState)); // 2.15
console.log(selectTax(exampleState)); // 0.172
console.log(selectTotal(exampleState)); // { total: 2.322 }

Join similar selectors

With reselect

import { createSelector } from reselect;

const getWorldData = state => state.world;

/*
* Solution 1: one selector for each country
* Problem: 195 selectors to maintain
*/
const getAfghanistanData = createSelector(
getWorldData,
world => extractData(world, 'afghanistan'),
);
// Albania, Algeria, Amer...
const getZimbabweData = createSelector(
getWorldData,
world => extractData(world, 'zimbawe'),
);

/*
* Solution 2: one selector shared by all countries
* Problem: each call to a different country invalidates
* the cache of the previous one
*/
const getCountryData = createSelector(
getWorldData,
(state, country) => country,
(world, country) => extractData(world, country),
);

const afghanistan = getCountryData(state, 'afghanistan');
const zimbabwe = getCountryData(state, 'zimbawe'); // Cache invalidated
const afghanistanAgain = getCountryData(state, 'afghanistan');

// afghanistan !== afghanistanAgain

/*
* Solution 3: use a factory function
* Problem:
* - Lost memoization across multiple components
* - Must call the factory once for each country on each container component
*/
const makeGetCountryData = country => {
return createSelector(
getWorldData,
world => extractData(world, country),
);
}

With re-reselect

import { createCachedSelector } from "re-reselect";

const getWorldData = (state) => state.world;

const getCountryData = createCachedSelector(
getWorldData,
(state, country) => country,
(world, country) => extractData(world, country)
)(
(state, country) => country // Cache selectors by country name
);

const afghanistan = getCountryData(state, "afghanistan");
const zimbabwe = getCountryData(state, "zimbawe");
const afghanistanAgain = getCountryData(state, "afghanistan");

// No selector factories and memoization is preserved among different components
// afghanistan === afghanistanAgain

With memosel

import memosel from "memosel";

const getWorldData = (state) => state.world;
const getCountryData = memosel()
// create selector factory that use keySelector, the keySelector accepts "country" argument and we use country as the key of selector cache
// keySelector can accept multiple arguments and the key array can contains multiple items
.key((country) => [country])
.use("world", getWorldData)
// the second and more arguments are the selected keys (they are returned from keySelector of keys())
.build(({ world }, country) => extractData(world, country));

const afghanistan = getCountryData("vietnam")(state);
const zimbabwe = getCountryData("zimbawe")(state);
const vietnamAgain = getCountryData("vietnam")(state);
// both of vietnam selectors are identical
console.log(getCountryData("vietnam") === getCountryData("vietnam"));

Avoid selector factories

This example shows how re-reselect would solve the scenario described in the Reselect docs: how to share a selector across multiple components while passing in props and retaining memoization?

With re-reselect

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;

With memosel

import memosel from "memosel";

const getVisibilityFilter = (state, listId) =>
state.todoLists[listId].visibilityFilter;

const getTodos = (state, listId) => state.todoLists[listId].todos;

const getVisibleTodos = memosel()
// extract listId and use it as selector key
.key((props) => [props.listId])
.use("visibilityFilter", getVisibilityFilter)
.use("todos", getTodos)
.build(({ 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;
}
});

// usages
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(props)(state),
};
};

Creating simple memoized function

import memosel from "memosel";

const memoizedFunction1 = memosel((x) => ({ result: x * 2 }));
const result1 = memoizedFunction1(1);
const result2 = memoizedFunction1(1);
console.log(result1 === result2); // true

memoizedFunction1(2);
const result3 = memoizedFunction1(1);
console.log(result1 === result3); // false, because memoized function has cache size = 1 by default

// create memoized function with unlimited cache size
const memoizedFunction2 = memosel((x) => ({ result: x * 2 }), { size: 0 });
const result1 = memoizedFunction2(1);
const result2 = memoizedFunction2(2);
const result3 = memoizedFunction2(3);

const result11 = memoizedFunction2(1);
const result22 = memoizedFunction2(2);
const result33 = memoizedFunction2(3);
console.log(result1 === result11); // true
console.log(result2 === result22); // true
console.log(result3 === result33); // true

API references

https://linq2js.github.io/memosel/

Generated using TypeDoc