'Use reselect selector with parameters
How do I pass additional parameters to combined selectors? I am trying to
• Get data
• Filter data
• Add custom value to my data set / group data by myValue
export const allData = state => state.dataTable
export const filterText = state => state.filter.get('text')
export const selectAllData = createSelector(
allData,
(data) => data
)
export const selectAllDataFiltered = createSelector(
[ selectAllData, filterText ],
(data, text) => {
return data.filter(item => {
return item.name === text
})
}
)
export const selectWithValue = createSelector(
[ selectAllDataFiltered ],
(data, myValue) => {
console.log(myValue)
return data
}
)
let data = selectWithValue(state, 'myValue')
console.log(myValue)
returns undefined
Solution 1:[1]
The answer to your questions is detailed in an FAQ here: https://github.com/reactjs/reselect#q-how-do-i-create-a-selector-that-takes-an-argument
In short, reselect doesn't support arbitrary arguments passed to selectors. The recommended approach is, instead of passing an argument, store that same data in your Redux state.
Solution 2:[2]
Updated: 16 February 2022
New Solution from Reselect 4.1: See detail
// selector.js
const selectItemsByCategory = createSelector(
[
// Usual first input - extract value from `state`
state => state.items,
// Take the second arg, `category`, and forward to the output selector
(state, category) => category
],
// Output selector gets (`items, category)` as args
(items, category) => items.filter(item => item.category === category)
);
// App.js
const items = selectItemsByCategory(state, 'javascript');
// Another way if you're using redux hook:
const items = useSelector(state => selectItemsByCategory(state, 'javascript'));
Updated: 6 March 2021
Solution from Reselect: See detail
// selector.js
import { createSelector } from 'reselect'
import memoize from 'lodash.memoize'
const expensiveSelector = createSelector(
state => state.items,
items => memoize(
minValue => items.filter(item => item.value > minValue)
)
)
// App.js
const expensiveFilter = expensiveSelector(state)
// Another way if you're using redux:
// const expensiveFilter = useSelector(expensiveSelector)
const slightlyExpensive = expensiveFilter(100)
const veryExpensive = expensiveFilter(1000000)
Old:
This is my approach. Creating a function with parameters and return function of reselect
.
export const selectWithValue = (CUSTOM_PARAMETER) => createSelector(
selectAllDataFiltered,
(data) => {
console.log(CUSTOM_PARAMETER)
return data[CUSTOM_PARAMETER]
}
)
const data = selectWithValue('myValue')(myState);
Solution 3:[3]
Here's one with the latest useSelector
hook.
The important thing is to get the parameter from the input selector. The input selector's second parameter is how we get it.
Here's how the selector would look,
const selectNumOfTodosWithIsDoneValue = createSelector(
(state) => state.todos,
(_, isDone) => isDone, // this is the parameter we need
(todos, isDone) => todos.filter((todo) => todo.isDone === isDone).length
)
And here's how we extract values with the useSelector
hook,
export const TodoCounterForIsDoneValue = ({ isDone }) => {
const NumOfTodosWithIsDoneValue = useSelector((state) =>
selectNumOfTodosWithIsDoneValue(state, isDone)
)
return <div>{NumOfTodosWithIsDoneValue}</div>
}
Also, keep, the second parameter (isDone
) as primitive values (string, number etc.) as much as possible.
Because, reselect, only runs the output selector when the input selector value changes.
This changes is checked via shallow comparison, which will always be false for reference values like Object and Array.
References:
Solution 4:[4]
what about returning a function from selector? getFilteredToDos
is an example for that
// redux part
const state = {
todos: [
{ state: 'done', text: 'foo' },
{ state: 'time out', text: 'bar' },
],
};
// selector for todos
const getToDos = createSelector(
getState,
(state) => state.todos,
);
// selector for filtered todos
const getFilteredToDos = createSelector(
getToDos,
(todos) => (todoState) => todos.filter((toDo) => toDo.state === todoState);
);
// and in component
const mapStateToProps = (state, ownProps) => ({
...ownProps,
doneToDos: getFilteredToDos()('done')
});
Solution 5:[5]
This is covered in the reselect docs under Accessing React Props in Selectors:
import { createSelector } from 'reselect'
const getVisibilityFilter = (state, props) =>
state.todoLists[props.listId].visibilityFilter
const getTodos = (state, props) =>
state.todoLists[props.listId].todos
const makeGetVisibleTodos = () => {
return createSelector(
[ 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
}
}
)
}
export default makeGetVisibleTodos
const makeMapStateToProps = () => {
const getVisibleTodos = makeGetVisibleTodos()
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props)
}
}
return mapStateToProps
}
In this case, the props passed to the selectors are the props passed to a React component, but the props can come from anywhere:
const getVisibleTodos = makeGetVisibleTodos()
const todos = getVisibleTodos(state, {listId: 55})
Looking at the types below for Reselect:
export type ParametricSelector<S, P, R> = (state: S, props: P, ...args: any[]) => R;
export function createSelector<S, P, R1, T>(
selectors: [ParametricSelector<S, P, R1>],
combiner: (res: R1) => T,
): OutputParametricSelector<S, P, T, (res: R1) => T>;
We can see there isn't a constraint on the type of props (the P
type in ParametricSelect
), so it doesn't need to be an object
.
Solution 6:[6]
Another option:
const parameterizedSelector = (state, someParam) => createSelector(
[otherSelector],
(otherSelectorResult) => someParam + otherSelectorResult
);
And then use like
const mapStateToProps = state => ({
parameterizedSelectorResult: parameterizedSelector(state, 'hello')
});
I am not sure about memoization/performance in this case though, but it works.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | David L. Walsh |
Solution 2 | |
Solution 3 | Badal Saibo |
Solution 4 | ColCh |
Solution 5 | mowwwalker |
Solution 6 | Tomas P. R. |