Getting Started with Reselect
A library for creating memoized "selector" functions. Commonly used with Redux, but usable with any plain JS immutable data as well.
- Selectors can compute derived data, allowing Redux to store the minimal possible state.
- Selectors are efficient. A selector is not recomputed unless one of its arguments changes.
- Selectors are composable. They can be used as input to other selectors.
The Redux docs usage page on Deriving Data with Selectors covers the purpose and motivation for selectors, why memoized selectors are useful, typical Reselect usage patterns, and using selectors with React-Redux.
Installation
Redux Toolkit
While Reselect is not exclusive to Redux, it is already included by default in the official Redux Toolkit package - no further installation needed.
import { createSelector } from '@reduxjs/toolkit'
Standalone
For standalone usage, install the reselect
package:
- NPM
- Yarn
- Bun
- PNPM
npm install reselect
yarn add reselect
bun add reselect
pnpm add reselect
Basic Usage
Reselect exports a createSelector
API, which generates memoized selector functions. createSelector
accepts one or more input selectors, which extract values from arguments, and a result function that receives the extracted values and should return a derived value. If the generated output selector is called multiple times, the output will only be recalculated when the extracted values have changed.
You can play around with the following example in this CodeSandbox:
- TypeScript
- JavaScript
import { createSelector } from 'reselect'
interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean }[]
}
const state: RootState = {
todos: [
{ id: 0, completed: false },
{ id: 1, completed: true },
],
alerts: [
{ id: 0, read: false },
{ id: 1, read: true },
],
}
const selectCompletedTodos = (state: RootState) => {
console.log('selector ran')
return state.todos.filter(todo => todo.completed === true)
}
selectCompletedTodos(state) // selector ran
selectCompletedTodos(state) // selector ran
selectCompletedTodos(state) // selector ran
const memoizedSelectCompletedTodos = createSelector(
[(state: RootState) => state.todos],
todos => {
console.log('memoized selector ran')
return todos.filter(todo => todo.completed === true)
},
)
memoizedSelectCompletedTodos(state) // memoized selector ran
memoizedSelectCompletedTodos(state)
memoizedSelectCompletedTodos(state)
console.log(selectCompletedTodos(state) === selectCompletedTodos(state)) //=> false
console.log(
memoizedSelectCompletedTodos(state) === memoizedSelectCompletedTodos(state),
) //=> true
import { createSelector } from 'reselect'
const state = {
todos: [
{ id: 0, completed: false },
{ id: 1, completed: true },
],
alerts: [
{ id: 0, read: false },
{ id: 1, read: true },
],
}
const selectCompletedTodos = state => {
console.log('selector ran')
return state.todos.filter(todo => todo.completed === true)
}
selectCompletedTodos(state) // selector ran
selectCompletedTodos(state) // selector ran
selectCompletedTodos(state) // selector ran
const memoizedSelectCompletedTodos = createSelector(
[state => state.todos],
todos => {
console.log('memoized selector ran')
return todos.filter(todo => todo.completed === true)
},
)
memoizedSelectCompletedTodos(state) // memoized selector ran
memoizedSelectCompletedTodos(state)
memoizedSelectCompletedTodos(state)
console.log(selectCompletedTodos(state) === selectCompletedTodos(state)) //=> false
console.log(
memoizedSelectCompletedTodos(state) === memoizedSelectCompletedTodos(state),
) //=> true
As you can see from the example above, memoizedSelectCompletedTodos
does not run the second or third time, but we still get the same return value as last time.
In addition to skipping unnecessary recalculations, memoizedSelectCompletedTodos
returns the existing result reference if there is no recalculation. This is important for libraries like React-Redux or React that often rely on reference equality checks to optimize UI updates.
Terminology
- Selector Function : A function that accepts one or more JavaScript values as arguments, and derives a result. When used with Redux, the first argument is typically the entire Redux store state.
-
Input Selectors
: Basic selector functions used as building blocks for creating a memoized
selector. They are passed as the first argument(s) to
createSelector
, and are called with all selector arguments. They are responsible for extracting and providing necessary values to the result function . -
Output Selector
: The actual memoized selectors created by
createSelector
. - Result Function : The function that comes after the input selectors . It takes the input selectors' return values as arguments and returns a result.
- Dependencies : Same as input selectors . They are what the output selector "depends" on.
The below example serves as a visual aid:
const outputSelector = createSelector(
[inputSelector1, inputSelector2, inputSelector3], // synonymous with `dependencies`.
resultFunc, // Result function
)