0.0.7 • Published 2 years ago

memosel v0.0.7

Weekly downloads
-
License
ISC
Repository
github
Last release
2 years ago

memosel

A library for creating memoized "selector" functions

Installation

with NPM

npm i memosel --save

with YARN

yarn add memosel

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/

0.0.7

2 years ago

0.0.6

2 years ago

0.0.5

2 years ago

0.0.4

2 years ago

0.0.3

2 years ago

0.0.2

2 years ago

0.0.1

2 years ago