What is reselect?
Reselect is a library for building memoized selectors in Redux. Selectors can compute derived data, allowing Redux to store the minimal possible state. Reselect selectors can be used to efficiently compute derived data from the Redux store.
What are reselect's main functionalities?
Memoization
Reselect provides memoization for selectors. Selectors created with `createSelector` only recompute when their inputs change, which saves on unnecessary recalculations.
const getVisibleTodos = createSelector(
[getVisibilityFilter, getTodos],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_ALL':
return todos;
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed);
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed);
}
}
)
Composability
Selectors can be composed together. One selector can use the output of another selector as input, allowing for complex queries to be built up in a clean and maintainable way.
const getTodoList = state => state.todoList;
const getKeyword = state => state.keyword;
const getFilteredTodoList = createSelector(
[getTodoList, getKeyword],
(todoList, keyword) => todoList.filter(todo => todo.includes(keyword))
);
Performance Optimization
Reselect helps in optimizing performance by avoiding unnecessary computations. This is particularly useful when dealing with expensive operations that should not be run every time the state updates.
const expensiveSelector = createSelector(
state => state.items,
items => expensiveComputation(items)
);
Other packages similar to reselect
re-reselect
Re-reselect is an extension of Reselect that allows for creating memoized selectors with a cache size greater than one. This is useful for memoizing selectors with different argument combinations.
redux-orm
Redux-ORM is a library that helps to manage relational data in Redux. It provides a more ORM-like experience for managing entities and their relationships. While it doesn't provide the same selector functionality as Reselect, it can be used alongside Reselect to manage complex data structures.
memoize-one
Memoize-one is a simple memoization library that remembers the latest arguments and result. It's not specifically designed for Redux or selectors, but it can be used to memoize any function, similar to how Reselect memoizes selectors.
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 install reselect
yarn add reselect
Documentation
The Reselect docs are available at https://reselect.js.org, and include usage guides and API references:
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 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:
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)
selectCompletedTodos(state)
selectCompletedTodos(state)
const memoizedSelectCompletedTodos = createSelector(
[(state: RootState) => state.todos],
todos => {
console.log('memoized selector ran')
return todos.filter(todo => todo.completed === true)
}
)
memoizedSelectCompletedTodos(state)
memoizedSelectCompletedTodos(state)
memoizedSelectCompletedTodos(state)
console.log(selectCompletedTodos(state) === selectCompletedTodos(state))
console.log(
memoizedSelectCompletedTodos(state) === memoizedSelectCompletedTodos(state)
)
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
The below example serves as a visual aid:
const outputSelector = createSelector(
[inputSelector1, inputSelector2, inputSelector3],
resultFunc
)
What's New in 5.0.0?
Version 5.0.0 introduces several new features and improvements:
-
Customization Enhancements:
- Added the ability to pass an options object to
createSelectorCreator
, allowing for customized memoize
and argsMemoize
functions, alongside their respective options (memoizeOptions
and argsMemoizeOptions
). - The
createSelector
function now supports direct customization of memoize
and argsMemoize
within its options object.
-
Memoization Functions:
- Introduced new experimental memoization functions:
weakMapMemoize
and unstable_autotrackMemoize
. - Incorporated
memoize
and argsMemoize
into the output selector fields for debugging purposes.
-
TypeScript Support and Performance:
- Discontinued support for TypeScript versions below 4.7, aligning with modern TypeScript features.
- Significantly improved TypeScript performance for nesting output selectors. The nesting limit has increased from approximately 8 to around 30 output selectors, greatly reducing the occurrence of the infamous
Type instantiation is excessively deep and possibly infinite
error.
-
Selector API Enhancements:
- Removed the second overload of
createStructuredSelector
due to its susceptibility to runtime errors.
-
Additional Functionalities:
- Added
dependencyRecomputations
and resetDependencyRecomputations
to the output selector fields. These additions provide greater control and insight over input selectors, complementing the new argsMemoize
API. - Introduced
inputStabilityCheck
, a development tool that runs the input selectors twice using the same arguments and triggers a warning If they return differing results for the same call. - Introduced
identityFunctionCheck
, a development tool that checks to see if the result function returns its own input.
These updates aim to enhance flexibility, performance, and developer experience. For detailed usage and examples, refer to the updated documentation sections for each feature.
-
Breaking Changes:
- Removed
ParametricSelector
and OutputParametricSelector
types. Their functionalities are now integrated into Selector
and OutputSelector
respectively, which inherently support additional parameters.
License
MIT
References
Click to Expand
Originally inspired by getters in NuclearJS, subscriptions in re-frame and this proposal from speedskater.