I have a selector:
const someSelector = createSelector(
getUserIdsSelector,
(ids) => ids.map((id) => yetAnotherSelector(store, id),
); // ^^^^^ (yetAnotherSelector expects 2 args)
That yetAnotherSelector
is another selector, that takes user id - id
and returns some data.
However, since it's createSelector
, I don't have access to store in it (I don't want it as a function because the memoization wouldn't work then).
Is there a way to access store somehow inside createSelector
? Or is there any other way to deal with it?
I have a function:
const someFunc = (store, id) => {
const data = userSelector(store, id);
// ^^^^^^^^^^^^ global selector
return data.map((user) => extendUserDataSelector(store, user));
// ^^^^^^^^^^^^^^^^^^^^ selector
}
Such function is killing my app, causing everything to re-render and driving me nuts. Help appreciated.
I have done some basic, custom memoization:
import { isEqual } from 'lodash';
const memoizer = {};
const someFunc = (store, id) => {
const data = userSelector(store, id);
if (id in memoizer && isEqual(data, memoizer(id)) {
return memoizer[id];
}
memoizer[id] = data;
return memoizer[id].map((user) => extendUserDataSelector(store, user));
}
And it does the trick, but isn't it just a workaround?
I faced the same case as yours, and unfortunately didn't find an efficient way to call a selector from another selector's body.
I said efficient way, because you can always have an input selector, which passes down the whole state (store), but this will recalculate your selector on each state's changes:
const someSelector = createSelector(
getUserIdsSelector,
state => state,
(ids, state) => ids.map((id) => yetAnotherSelector(state, id)
)
However, I found out two possible approaches, for the use-case described below. I guess your case is similar, so you can take some insights.
So the case is as follows: You have a selector, that gets a specific User from the Store by an id, and the selector returns the User in a specific structure. Let's say getUserById
selector. For now everything's fine and simple as possible. But the problem occurs when you want to get several Users by their ids and also reuse the previous selector. Let's name it getUsersByIds
selector.
The first possible solution is to have a selector that always expects an array of ids (getUsersByIds
) and a second one, that reuses the previous, but it will get only 1 User (getUserById
). So when you want to get only 1 User from the Store, you have to use getUserById
, but you have to pass an array with only one user id.
Here's the implementation:
import { createSelectorCreator, defaultMemoize } from 'reselect'
import { isEqual } from 'lodash'
/**
* Create a "selector creator" that uses `lodash.isEqual` instead of `===`
*
* Example use case: when we pass an array to the selectors,
* they are always recalculated, because the default `reselect` memoize function
* treats the arrays always as new instances.
*
* @credits https://github.com/reactjs/reselect#customize-equalitycheck-for-defaultmemoize
*/
const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
isEqual
)
export const getUsersIds = createDeepEqualSelector(
(state, { ids }) => ids), ids => ids)
export const getUsersByIds = createSelector(state => state.users, getUsersIds,
(users, userIds) => {
return userIds.map(id => ({ ...users[id] })
}
)
export const getUserById = createSelector(getUsersByIds, users => users[0])
Usage:
// Get 1 User by id
const user = getUserById(state, { ids: [1] })
// Get as many Users as you want by ids
const users = getUsersByIds(state, { ids: [1, 2, 3] })
The idea here is to separate the common and reusable part of the selector body in a stand-alone function, so this function to be callable from all other selectors.
Here's the implementation:
export const getUsersByIds = createSelector(state => state.users, getUsersIds,
(users, userIds) => {
return userIds.map(id => _getUserById(users, id))
}
)
export const getUserById = createSelector(state => state.users, (state, props) => props.id, _getUserById)
const _getUserById = (users, id) => ({ ...users[id]})
Usage:
// Get 1 User by id
const user = getUserById(state, { id: 1 })
// Get as many Users as you want by ids
const users = getUsersByIds(state, { ids: [1, 2, 3] })
Approach #1. has less boilerplate (we don't have a stand-alone function) and has clean implementation.
Approach #2. is more reusable. Imagine the case, where we don't have an User's id when we call a selector, but we get it from the selector's body as a relation. In that case, we can easily reuse the stand-alone function. Here's а pseudo example:
export const getBook = createSelector(state => state.books, state => state.users, (state, props) => props.id,
(books, users, id) => {
const book = books[id]
// Here we have the author id (User's id)
// and out goal is to reuse `getUserById()` selector body,
// so our solution is to reuse the stand-alone `_getUserById` function.
const authorId = book.authorId
const author = _getUserById(users, authorId)
return {
...book,
author
}
}