Hi there, is it possible to compose selectors with arguments? If have a simple selector which returns a selection, another selector which takes a nested collection (key is passed as a argument) and another selection which consumes the selection:
import createSelector from '../createSelector';
// selection.js: Returns a map of selections: {availability: [], user: []}
export default createSelector(
[state => state.ui.selection],
selection => selection
);
import createSelector from '../createSelector';
import selectionSelector from './selection';
// selected.js: Returns a specific selection from the map (e. g. "user")
export default createSelector(
[selectionSelector],
(selection, bucket) => selection && selection.hasOwnProperty(bucket) ? selection[bucket] : []
);
import Immutable from 'immutable';
import selectedSelector from '../ui/selected';
import {createImmutableSelector} from '../createSelector';
// selectedUsers.js: Returns a collection of selected users
export default createImmutableSelector(
[
state => state.users,
selectedSelector('user') // This does not work
],
(users, selection) => users.reduce((result, user) => {
// Select only the users which are selected
}, Immutable.List())
);
Currently you aren't able to parameterise selectors. You could make selectedSelector a factory function that returns a selector:
function selectedSelectorFactory(key) {
return createSelector(
[state => state.something],
something => something[key]
);
}
I agree with @ellbee. Using a factory function you could write:
export default createImmutableSelector(
[
state => state.users,
selectedSelectorFactory('user') //Here a new user selector is created on the fly
],
(users, selection) => users.reduce((result, user) => {
// Select only the users which are selected
}, Immutable.List())
);
Thank you @ellbee and @speedskater, that solves the problem elegantly.
Great!
This topic came up in Nuclear a while back as I recall. One additional thing to consider is whether the parameter you are passing in is actually transient or if it should actually be state instead. In that case, you could add it to the state tree and make it a normal dependency of the selector.
Apologies for reviving this thread, but can anyone provide insight into how using a factory function effects memoization?
const itemsAll = createSelector(state, (state) => state.items)
vs
const itemById = (id) => createSelector(itemsAll, (items) => find(items, { Id: id }))
I have the same question as @josh-sachs
@madebyherzblut could you please shed some light on @josh-sachs ' s question?
Yes, sure! Since the factory function returns a new function on every call, memoization will not work. But you can just wrap your factory with another memoize function.
The defaultMemoize function from reselect only caches the last result, which may not work for your factory. In the demo I included a memoizer which caches more than one result: Demo
const { createSelector, defaultMemoize } = window.Reselect;
// Source: https://addyosmani.com/blog/faster-javascript-memoization/
function memoize(f) {
return function() {
const args = Array.prototype.slice.call(arguments);
//we've confirmed this isn't really influencing
//speed positively
f.memoize = f.memoize || {};
//this is the section we're interested in
return args in f.memoize
? f.memoize[args]
: (f.memoize[args] = f.apply(this, args));
};
}
const shopItemsSelector = state => {
console.log("shopItemsSelector");
return state.shop.items;
};
const selectorWithArgWithoutMemoize = expensive => {
console.log("Create selectorWithArgWithoutMemoize with " + expensive);
return createSelector(shopItemsSelector, items => {
console.log("selectorWithArgWithoutMemoize");
return items
.filter(item => item.value >= expensive)
.reduce((acc, item) => acc + item.value, 0);
});
};
const selectorWithArgWithDefaultMemoize = defaultMemoize(expensive => {
console.log("Create selectorWithArgWithDefaultMemoize with " + expensive);
return createSelector(shopItemsSelector, items => {
console.log("selectorWithArgWithDefaultMemoize");
return items
.filter(item => item.value >= expensive)
.reduce((acc, item) => acc + item.value, 0);
});
});
const selectorWithArgWithMemoize = memoize(expensive => {
console.log("Create selectorWithArgWithMemoize with " + expensive);
return createSelector(shopItemsSelector, items => {
console.log("selectorWithArgWithMemoize");
return items
.filter(item => item.value >= expensive)
.reduce((acc, item) => acc + item.value, 0);
});
});
let exampleState = {
shop: {
items: [
{ name: "apple", value: 1.2 },
{ name: "peach", value: 5.2 },
{ name: "banana", value: 3.15 },
{ name: "orange", value: 0.95 }
]
}
};
console.log(selectorWithArgWithoutMemoize(3)(exampleState));
console.log(selectorWithArgWithoutMemoize(3.5)(exampleState));
console.log('---');
console.log(selectorWithArgWithDefaultMemoize(3)(exampleState));
console.log(selectorWithArgWithDefaultMemoize(3.5)(exampleState));
console.log(selectorWithArgWithDefaultMemoize(3)(exampleState));
console.log(selectorWithArgWithDefaultMemoize(3.5)(exampleState));
console.log('---');
console.log(selectorWithArgWithMemoize(3)(exampleState));
console.log(selectorWithArgWithMemoize(3.5)(exampleState));
console.log(selectorWithArgWithMemoize(3)(exampleState));
console.log(selectorWithArgWithMemoize(3.5)(exampleState));
The last two calls to selectorWithArgWithMemoize will use the cached result of the previous calls.
This approach works pretty well.
However I am experiencing weird behaviours when passing memoized parameterised selectors to other selectors. For example:
const DEFAULT_USER_ID = 1;
export const getUserById = memoized(id => createSelector(getUser, user => user[id] || emptyMap));
export const getDefaultUser = createSelector(getUserById(DEFAULT_USER_ID), u => u);

The id parameter of the memoized function is anything but the id I passed in the other selector.
Does someone knows why? Thanks.
Most helpful comment
Currently you aren't able to parameterise selectors. You could make
selectedSelectora factory function that returns a selector: