Selectors for mapping over collections.
npm install reselect-map reselect
This package exposes a few special selector creators. They're mostly the same as reselect's createSelector
, with one major difference: the first dependency gets mapped over.
To give you a better idea, here's an example of how a selector to multiply an array of numbers would look in basic reselect and then using reselect-map.
import { createSelector } from "reselect";
import { createArraySelector } from "reselect-map";
const exampleState = {
numbers: [1, 2, 3],
multiplier: 5,
};
const numbers = (state) => state.numbers;
const multiplier = (state) => state.multiplier;
// reselect
const mul1 = createSelector([numbers, multiplier], (numbers, multiplier) =>
numbers.map((n) => n * multiplier)
);
// reselect-map
const mul2 = createArraySelector(
[numbers, multiplier],
(number, multiplier) => number * multiplier
);
// Result: [5, 10, 15]
Notice the second version uses number
instead of numbers
; the result function is passed each element of numbers
instead of the whole thing, and then the results are reassembled into an array.
So why would you use this? The answer is that you probably shouldn't! However, in certain situations it may significantly improve performance. Learn more in the motivation section.
The only thing to know is that the first dependency/selector passed is the one that gets mapped over, and all other selectors work just like they do in reselect proper. Additionally, key-based selectors like createObjectSelector
and createMapSelector
pass the key as the final argument.
NOTICE: This package makes use of the builtin Set and Map. If you need to support environments without Set or Map, you are going to have to polyfill it.
Takes in an array, runs each element through the result function, and returns the results in a new array.
import { createArraySelector } from "reselect-map";
const exampleState = {
numbers: [1, 2, 3],
multiplier: 5,
};
const mul = createArraySelector(
(state) => state.numbers,
(state) => state.multiplier,
(number, multiplier) => number * multiplier
);
console.log(mul(exampleState)); // [5, 10, 15]
Takes an object, runs the value at each key through the result function, and returns an object with the results. The key is passed as the last argument to the selector function.
import { createObjectSelector } from "reselect-map";
const exampleState = {
numbers: { a: 1, b: 2 },
multiplier: 5,
};
const mul = createObjectSelector(
(state) => state.numbers,
(state) => state.multiplier,
(number, multiplier, key) => `${key}:${number * multiplier}`
);
console.log(mul(exampleState)); // { a: 'a:5', b: 'b:10' }
Takes anything with an array-like map
function, and returns whatever that returns. This conveniently makes it compatible with Immutable js collections and similar without erasing the input type.
import { createListSelector } from "reselect-map";
import Immutable from "immutable";
const exampleState = {
numbers: Immutable.List([1, 2, 3]),
multiplier: 5,
};
const mul = createListSelector(
(state) => state.numbers,
(state) => state.multiplier,
(number, multiplier) => number * multiplier
);
console.log(String(mul(exampleState))); // List [5, 10, 15]
Like the sequence selector, but it expects the map function to provide a second argument to the callback that represents the key. This key is passed as the last argument to the selector function. This is mostly to support Immutable's Collection.Keyed types.
import { createMapSelector } from "reselect-map";
import Immutable from "immutable";
const exampleState = {
numbers: Immutable.Map({ a: 1, b: 2 }),
multiplier: 5,
};
const mul = createMapSelector(
(state) => state.numbers,
(state) => state.multiplier,
(number, multiplier, key) => `${key}:${number * multiplier}`
);
console.log(String(mul(exampleState))); // Map { "a": "a:5", "b": "b:10" }
I'm hoping that what I've got here covers most common use cases, but it's not difficult to expand. Please let me know if there are any other selector types you'd like, or submit a pull request!
When doing very expensive computations on elements of a collection, reselect might not give you the granularity of caching that you need. Imagine a selector that looks like this:
import { createSelector } from "reselect";
const expensiveSelector = createSelector(
(state) => state.largeArray,
(largeArray) => largeArray.map(expensiveFunction)
);
Notice that every time largeArray
is changed, every single element of the array will be run back through expensiveFunction
. If largeArray
is very large or expensiveFunction
is very expensive, this could be very slow.
What would be better is if we only recomputed those elements that are new or have changed. That's what this package does. Your expensiveFunction
only runs on the elements it needs to.
When you don't need it. If your selector takes an array and multiplies every element by five (like in all of the examples), this package will slow your code down. In situations where you're having performance issues with reselect and you're thinking about how to cache on an element level, this package is here for you.