createSelector
Accepts one or more "input selectors" (either as separate arguments or a single array), a single "result function", and an optional options object, and generates a memoized selector function.
The Redux docs usage page on Deriving Data with Selectors covers the purpose and motivation for selectors, why memoized selectors are useful, and typical Reselect usage patterns.
const selectTodosByCategory = createSelector(
[
// Pass input selectors with typed arguments
(state: RootState) => state.todos,
(state: RootState, category: string) => category
],
// Extracted values are passed to the result function for recalculation
(todos, category) => {
return todos.filter(t => t.category === category)
}
)
API Reference
Parameters
| Name | Description |
|---|---|
inputSelectors | An array of input selectors, can also be passed as separate arguments. |
resultFunc | A function that takes the results of the input selectors as separate arguments. |
createSelectorOptions? | An optional options object that allows for further customization per selector. See Customizing Selector Options below. |
Returns
A memoized output selector.
Output Selector Fields
The output selectors created by createSelector have several additional properties attached to them:
| Name | Description |
|---|---|
resultFunc | The final function passed to createSelector. |
memoizedResultFunc | The memoized version of resultFunc. |
lastResult | Returns the last result calculated by memoizedResultFunc. |
dependencies | The array of the input selectors used by createSelector to compose resultFunc. |
recomputations | Counts the number of times memoizedResultFunc has been recalculated. |
resetRecomputations | Resets the count of recomputations count to 0. |
dependencyRecomputations | Counts the number of times the input selectors (dependencies) have been recalculated. This is distinct from recomputations, which tracks the recalculations of the result function. |
resetDependencyRecomputations | Resets the dependencyRecomputations count to 0. |
memoize | Function used to memoize the resultFunc. |
argsMemoize | Function used to memoize the arguments passed into the output selector. |
Type Parameters
| Name | Description |
|---|---|
InputSelectors | The type of the input selectors array. |
Result | The return type of the result function as well as the output selector. |
OverrideMemoizeFunction | The type of the optional memoize function that could be passed into the options object to override the original memoize function that was initially passed into createSelectorCreator. |
OverrideArgsMemoizeFunction | The type of the optional argsMemoize function that could be passed into the options object to override the original argsMemoize function that was initially passed into createSelectorCreator. |
Defining a Pre-Typed createSelector
As of Reselect 5.1.0, you can create a "pre-typed" version of createSelector where the state type is predefined. This allows you to set the state type once, eliminating the need to specify it with every createSelector call.
To do this, you can call createSelector.withTypes<StateType>():
- TypeScript
- JavaScript
import { createSelector } from 'reselect'
export interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean }[]
}
export const createAppSelector = createSelector.withTypes<RootState>()
const selectTodoIds = createAppSelector(
[
// Type of `state` is set to `RootState`, no need to manually set the type
state => state.todos
],
todos => todos.map(({ id }) => id)
)
import { createSelector } from 'reselect'
export const createAppSelector = createSelector.withTypes()
const selectTodoIds = createAppSelector(
[
// Type of `state` is set to `RootState`, no need to manually set the type
state => state.todos
],
todos => todos.map(({ id }) => id)
)
Import and use the pre-typed createAppSelector instead of the original, and the type for state will be used automatically.
Currently this approach only works if input selectors are provided as a single array.
If you pass the input selectors as separate inline arguments, the parameter types of the result function will not be inferred. As a workaround you can either
- Wrap your input selectors in a single array
- You can annotate the parameter types of the result function:
- TypeScript
- JavaScript
import { createSelector } from 'reselect'
interface Todo {
id: number
completed: boolean
}
interface Alert {
id: number
read: boolean
}
export interface RootState {
todos: Todo[]
alerts: Alert[]
}
export const createAppSelector = createSelector.withTypes<RootState>()
const selectTodoIds = createAppSelector(
// Type of `state` is set to `RootState`, no need to manually set the type
state => state.todos,
// ❌ Known limitation: Parameter types are not inferred in this scenario
// so you will have to manually annotate them.
(todos: Todo[]) => todos.map(({ id }) => id)
)
import { createSelector } from 'reselect'
export const createAppSelector = createSelector.withTypes()
const selectTodoIds = createAppSelector(
// Type of `state` is set to `RootState`, no need to manually set the type
state => state.todos,
// ❌ Known limitation: Parameter types are not inferred in this scenario
// so you will have to manually annotate them.
todos => todos.map(({ id }) => id)
)
You can also use this API with createSelectorCreator to create a pre-typed custom selector creator:
- TypeScript
- JavaScript
import microMemoize from 'micro-memoize'
import { shallowEqual } from 'react-redux'
import { createSelectorCreator, lruMemoize } from 'reselect'
export interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean }[]
}
export const createAppSelector = createSelectorCreator({
memoize: lruMemoize,
argsMemoize: microMemoize,
memoizeOptions: {
maxSize: 10,
equalityCheck: shallowEqual,
resultEqualityCheck: shallowEqual
},
argsMemoizeOptions: {
isEqual: shallowEqual,
maxSize: 10
},
devModeChecks: {
identityFunctionCheck: 'never',
inputStabilityCheck: 'always'
}
}).withTypes<RootState>()
const selectReadAlerts = createAppSelector(
[
// Type of `state` is set to `RootState`, no need to manually set the type
state => state.alerts
],
alerts => alerts.filter(({ read }) => read)
)
import microMemoize from 'micro-memoize'
import { shallowEqual } from 'react-redux'
import { createSelectorCreator, lruMemoize } from 'reselect'
export const createAppSelector = createSelectorCreator({
memoize: lruMemoize,
argsMemoize: microMemoize,
memoizeOptions: {
maxSize: 10,
equalityCheck: shallowEqual,
resultEqualityCheck: shallowEqual
},
argsMemoizeOptions: {
isEqual: shallowEqual,
maxSize: 10
},
devModeChecks: {
identityFunctionCheck: 'never',
inputStabilityCheck: 'always'
}
}).withTypes()
const selectReadAlerts = createAppSelector(
[
// Type of `state` is set to `RootState`, no need to manually set the type
state => state.alerts
],
alerts => alerts.filter(({ read }) => read)
)
Usage Guide
Memoization Functions
createSelector does not implement memoization itself — it delegates to a memoize function: a higher-order function that wraps another function and returns a caching version of it, so that repeated calls with known arguments return the cached result instead of recomputing.
Each selector uses a memoize function at two levels, following the "Cascading Memoization" pattern:
argsMemoizememoizes the generated selector itself, based on the arguments it was called with (such asstate). If the arguments match a previous call, nothing else runs.memoizememoizes the result function, based on the values returned by the input selectors. If those values match, the cached result is returned without recalculating.
Reselect ships two memoize functions: weakMapMemoize (the default) and lruMemoize.
Both levels can be overridden independently, per selector via the memoize/argsMemoize options, or for a whole family of selectors via createSelectorCreator. Third-party memoizers such as micro-memoize work as well.
Since v5.0.0, createSelector uses weakMapMemoize as the default for both memoize and argsMemoize. In v4.x the default was lruMemoize (previously named defaultMemoize) with a cache size of 1, meaning a selector only remembered its most recent call.
weakMapMemoize instead provides an effectively unlimited cache size keyed on argument identity, which fixes the common "cache size of 1" problem when the same selector is called with many different arguments (e.g. useSelector(state => selectItemById(state, id)) across a list). Results keyed by object arguments are released as those arguments are garbage-collected; results keyed by primitive arguments stay cached until cleared. See weakMapMemoize for the full behavior and tradeoffs.
Because these are the defaults, you do not need to pass memoize/argsMemoize unless you want to override them (for example, to use lruMemoize with a custom equalityCheck).
Customizing Selector Options
There are two places to apply these options, depending on how widely you want them to apply.
Per instance, with the options object
createSelector accepts an options object as its final argument. Options passed there apply to that one selector instance: memoize and argsMemoize choose the memoize function for each level, while memoizeOptions and argsMemoizeOptions are forwarded to those functions as their configuration.
const selectTodoIds = createSelector(
[(state: RootState) => state.todos],
todos => todos.map(todo => todo.id),
{
memoize: lruMemoize,
memoizeOptions: { resultEqualityCheck: shallowEqual }
}
)
This is the right place for a one-off: a single selector that needs a custom equality check or a different memoizer than the rest of the app.
Baked in, with createSelectorCreator
If several selectors need the same configuration, repeating the options object at every call site gets noisy and easy to forget. createSelectorCreator accepts the same options once and returns a customized createSelector with those options baked in:
const createShallowEqualSelector = createSelectorCreator({
memoize: lruMemoize,
memoizeOptions: { resultEqualityCheck: shallowEqual }
})
// Every selector made with this creator shares the configuration
const selectTodoIds = createShallowEqualSelector(
[(state: RootState) => state.todos],
todos => todos.map(todo => todo.id)
)
In fact, the default createSelector is itself the result of calling createSelectorCreator with weakMapMemoize.
The two compose: a selector made with a custom creator can still pass its own options object, and those per-instance options override the baked-in ones for that selector only.