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
npm install reselect
# Yarn
yarn add reselect
# Bun
bun add reselect
# PNPM
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 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) // 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
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.
Table of Contents
- Installation
- Basic Usage
- Terminology
- How Does Reselect Work?
- API
- Debugging Tools
- What's New in 5.0.0?
- Optimizing Reselect
- FAQ
- Why isn’t my selector recomputing when the input state changes?
- Why is my selector recomputing when the input state stays the same?
- Can I use Reselect without Redux?
- How do I create a selector that takes an argument?
- Can the memoization behavior be customized?
- How do I test a selector?
- Can I share a selector across multiple component instances?
- Are there TypeScript Typings?
- I am seeing a TypeScript error:
Type instantiation is excessively deep and possibly infinite
- How can I make a curried selector?
- How can I make pre-typed version of
createSelector
for my root state? - What if I want to use
createSelector
without memoization?
- External References
- Related Projects
- License
- Prior Art and Inspiration
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
)
How Does Reselect Work?
Reselect, at its core, is a library for creating memoized selectors in JavaScript applications. Its primary role is to efficiently compute derived data based on provided inputs. A key aspect of Reselect's internal mechanism is how it orchestrates the flow of arguments from the final selector to its constituent input selectors.
const finalSelector = (...args) => {
const extractedValues = inputSelectors.map(inputSelector =>
inputSelector(...args)
)
return resultFunc(...extractedValues)
}
In this pattern, the finalSelector
is composed of several input selectors, all receiving the same arguments as the final selector. Each input selector processes its part of the data, and the results are then combined and further processed by the result function. Understanding this argument flow is crucial for appreciating how Reselect optimizes data computation and minimizes unnecessary recalculations.
Cascading Memoization
Reselect uses a two-stage "cascading" approach to memoizing functions:
Detailed Explanation: Cascading Memoization
The way Reselect works can be broken down into multiple parts:
-
Initial Run: On the first call, Reselect runs all the input selectors, gathers their results, and passes them to the result function.
-
Subsequent Runs: For subsequent calls, Reselect performs two levels of checks:
-
First Level: It compares the current arguments with the previous ones (done by
argsMemoize
).-
If they're the same, it returns the cached result without running the input selectors or the result function.
-
If they differ, it proceeds ("cascades") to the second level.
-
-
Second Level: It runs the input selectors and compares their current results with the previous ones (done by
memoize
).[!NOTE] If any one of the input selectors return a different result, all input selectors will recalculate.
- If the results are the same, it returns the cached result without running the result function.
- If the results differ, it runs the result function.
-
This behavior is what we call Cascading Double-Layer Memoization.
Reselect Vs Standard Memoization
Standard Memoization
Standard memoization only compares arguments. If they're the same, it returns the cached result.
Memoization with Reselect
Reselect adds a second layer of checks with the input selectors. This is crucial in Redux applications where state references change frequently.
A normal memoization function will compare the arguments, and if they are the same as last time, it will skip running the function and return the cached result. However, Reselect enhances this by introducing a second tier of checks via its input selectors. It's possible that the arguments passed to these input selectors may change, yet their results remain the same. When this occurs, Reselect avoids re-executing the result function, and returns the cached result.
This feature becomes crucial in Redux applications, where the state
changes its reference anytime an action
is dispatched.
[!NOTE] The input selectors take the same arguments as the output selector.
Redux
Why Reselect Is Often Used WithWhile Reselect can be used independently from Redux, it is a standard tool used in most Redux applications to help optimize calculations and UI updates:
Detailed Explanation: Reselect and Redux Optimization
Imagine you have a selector like this:
const selectCompletedTodos = (state: RootState) =>
state.todos.filter(todo => todo.completed === true)
So you decide to memoize it:
const selectCompletedTodos = someMemoizeFunction((state: RootState) =>
state.todos.filter(todo => todo.completed === true)
)
Then you update state.alerts
:
store.dispatch(toggleRead(0))
Now when you call selectCompletedTodos
, it re-runs, because we have effectively broken memoization.
selectCompletedTodos(store.getState())
// Will not run, and the cached result will be returned.
selectCompletedTodos(store.getState())
store.dispatch(toggleRead(0))
// It recalculates.
selectCompletedTodos(store.getState())
But why? selectCompletedTodos
only needs to access state.todos
, and has nothing to do with state.alerts
, so why have we broken memoization? Well that's because in Redux anytime you make a change to the root state
, it gets shallowly updated, which means its reference changes, therefore a normal memoization function will always fail the comparison check on the arguments.
But with Reselect, we can do something like this:
const selectCompletedTodos = createSelector(
[(state: RootState) => state.todos],
todos => todos.filter(todo => todo.completed === true)
)
And now we have achieved memoization:
selectCompletedTodos(store.getState())
// Will not run, and the cached result will be returned.
selectCompletedTodos(store.getState())
store.dispatch(toggleRead(0))
// The `input selectors` will run, but the `result function` is
// skipped and the cached result will be returned.
selectCompletedTodos(store.getState())
Even when the overall state
changes, Reselect ensures efficient memoization through its unique approach. The result function doesn't re-run if the relevant part of the state
(in this case state.todos
), remains unchanged. This is due to Reselect's Cascading Double-Layer Memoization. The first layer checks the entire state
, and the second layer checks the results of the input selectors. If the first layer fails (due to a change in the overall state
) but the second layer succeeds (because state.todos
is unchanged), Reselect skips recalculating the result function. This dual-check mechanism makes Reselect particularly effective in Redux applications, ensuring computations are only done when truly necessary.
API
createSelector(...inputSelectors | [inputSelectors], resultFunc, createSelectorOptions?)
Description
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.
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. |
Returns
A memoized 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 . |
createSelectorCreator(memoize | options, ...memoizeOptions)
Description
Accepts either a memoize
function and ...memoizeOptions
rest parameter, or since 5.0.0 an options
object containing a memoize
function and creates a custom selector creator function.
Parameters (since 5.0.0)
Name | Description |
---|---|
options |
An options object containing the memoize function responsible for memoizing the resultFunc inside createSelector (e.g., lruMemoize or weakMapMemoize ). It also provides additional options for customizing memoization. While the memoize property is mandatory, the rest are optional. |
options.argsMemoize? |
The optional memoize function that is used to memoize the arguments passed into the output selector generated by createSelector (e.g., lruMemoize or weakMapMemoize ). Default weakMapMemoize |
options.argsMemoizeOptions? |
Optional configuration options for the argsMemoize function. These options are passed to the argsMemoize function as the second argument. since 5.0.0 |
options.devModeChecks? |
Overrides the settings for the global development mode checks for the selector. since 5.0.0 |
options.memoize |
The memoize function that is used to memoize the resultFunc inside createSelector (e.g., lruMemoize or weakMapMemoize ). since 5.0.0 |
options.memoizeOptions? |
Optional configuration options for the memoize function. These options are passed to the memoize function as the second argument. since 5.0.0 |
Parameters
Name | Description |
---|---|
memoize |
The memoize function responsible for memoizing the resultFunc inside createSelector (e.g., lruMemoize or weakMapMemoize ). |
...memoizeOptionsFromArgs |
Optional configuration options for the memoization function. These options are then passed to the memoize function as the second argument onwards. |
Returns
A customized createSelector
function.
Type Parameters
Name | Description |
---|---|
MemoizeFunction |
The type of the memoize function that is used to memoize the resultFunc inside createSelector (e.g., lruMemoize or weakMapMemoize ). |
ArgsMemoizeFunction |
The type of the optional memoize function that is used to memoize the arguments passed into the output selector generated by createSelector (e.g., lruMemoize or weakMapMemoize ). If none is explicitly provided, weakMapMemoize will be used. |
Examples
options
(since 5.0.0)
Using const customCreateSelector = createSelectorCreator({
memoize: customMemoize, // Function to be used to memoize `resultFunc`
memoizeOptions: [memoizeOption1, memoizeOption2], // Options passed to `customMemoize` as the second argument onwards
argsMemoize: customArgsMemoize, // Function to be used to memoize the selector's arguments
argsMemoizeOptions: [argsMemoizeOption1, argsMemoizeOption2] // Options passed to `customArgsMemoize` as the second argument onwards
})
const customSelector = customCreateSelector(
[inputSelector1, inputSelector2],
resultFunc // `resultFunc` will be passed as the first argument to `customMemoize`
)
customSelector(
...selectorArgs // Will be memoized by `customArgsMemoize`
)
memoize
and ...memoizeOptions
Using createSelectorCreator
can be used to make a customized version of createSelector
.
The memoize
argument is a memoization function to replace weakMapMemoize
.
The ...memoizeOptions
rest parameters are zero or more configuration options to be passed to memoizeFunc
. The selectors resultFunc
is passed as the first argument to memoize
and the memoizeOptions
are passed as the second argument onwards:
const customSelectorCreator = createSelectorCreator(
customMemoize, // Function to be used to memoize `resultFunc`
option1, // `option1` will be passed as second argument to `customMemoize`
option2, // `option2` will be passed as third argument to `customMemoize`
option3 // `option3` will be passed as fourth argument to `customMemoize`
)
const customSelector = customSelectorCreator(
[inputSelector1, inputSelector2],
resultFunc // `resultFunc` will be passed as first argument to `customMemoize`
)
Internally customSelector
calls the memoize function as follows:
customMemoize(resultFunc, option1, option2, option3)
Additional Examples
equalityCheck
for lruMemoize
Customize import { createSelectorCreator, lruMemoize } from 'reselect'
import isEqual from 'lodash.isequal'
// create a "selector creator" that uses lodash.isequal instead of ===
const createDeepEqualSelector = createSelectorCreator(lruMemoize, isEqual)
// use the new "selector creator" to create a selector
const selectSum = createDeepEqualSelector(
[state => state.values.filter(val => val < 5)],
values => values.reduce((acc, val) => acc + val, 0)
)
Use memoize function from Lodash for an unbounded cache
import { createSelectorCreator } from 'reselect'
import memoize from 'lodash.memoize'
const hashFn = (...args) =>
args.reduce((acc, val) => acc + '-' + JSON.stringify(val), '')
const customSelectorCreator = createSelectorCreator(memoize, hashFn)
const selector = customSelectorCreator(
[state => state.a, state => state.b],
(a, b) => a + b
)
createStructuredSelector({ inputSelectorsObject }, selectorCreator = createSelector)
Description
A convenience function that simplifies returning an object made up of selector results.
Parameters
Name | Description |
---|---|
inputSelectorsObject |
A key value pair consisting of input selectors. |
selectorCreator? |
A custom selector creator function. It defaults to createSelector . |
Returns
A memoized structured selector.
Type Parameters
Name | Description |
---|---|
InputSelectorsObject |
The shape of the input selectors object. |
MemoizeFunction |
The type of the memoize function that is used to create the structured selector. It defaults to weakMapMemoize . |
ArgsMemoizeFunction |
The type of the of the memoize function that is used to memoize the arguments passed into the generated structured selector. It defaults to weakMapMemoize . |
Examples
Modern Use Case
import { createSelector, createStructuredSelector } from 'reselect'
interface RootState {
todos: {
id: number
completed: boolean
title: string
description: string
}[]
alerts: { id: number; read: boolean }[]
}
// This:
const structuredSelector = createStructuredSelector(
{
todos: (state: RootState) => state.todos,
alerts: (state: RootState) => state.alerts,
todoById: (state: RootState, id: number) => state.todos[id]
},
createSelector
)
// Is essentially the same as this:
const selector = createSelector(
[
(state: RootState) => state.todos,
(state: RootState) => state.alerts,
(state: RootState, id: number) => state.todos[id]
],
(todos, alerts, todoById) => {
return {
todos,
alerts,
todoById
}
}
)
In your component:
interface Props {
id: number
}
const MyComponent: FC<Props> = ({ id }) => {
const { todos, alerts, todoById } = useSelector(state =>
structuredSelector(state, id)
)
return (
<div>
Next to do is:
<h2>{todoById.title}</h2>
<p>Description: {todoById.description}</p>
<ul>
<h3>All other to dos:</h3>
{todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
)
}
Simple Use Case
const selectA = state => state.a
const selectB = state => state.b
// The result function in the following selector
// is simply building an object from the input selectors
const structuredSelector = createSelector(selectA, selectB, (a, b) => ({
a,
b
}))
const result = structuredSelector({ a: 1, b: 2 }) // will produce { x: 1, y: 2 }
createStructuredSelector
takes an object whose properties are input selectors and returns a structured selector. The structured selector returns an object with the same keys as the inputSelectorsObject
argument, but with the selectors replaced with their values.
const selectA = state => state.a
const selectB = state => state.b
const structuredSelector = createStructuredSelector({
x: selectA,
y: selectB
})
const result = structuredSelector({ a: 1, b: 2 }) // will produce { x: 1, y: 2 }
Structured selectors can be nested:
const nestedSelector = createStructuredSelector({
subA: createStructuredSelector({
selectorA,
selectorB
}),
subB: createStructuredSelector({
selectorC,
selectorD
})
})
Memoization Functions
Reselect comes with a selection of memoization functions, each uniquely designed to address different scenarios and performance requirements. By effectively leveraging these functions, you can significantly enhance the efficiency and responsiveness of your applications.
lruMemoize(func, equalityCheckOrOptions = referenceEqualityCheck)
Description
The standard memoize function used by createSelector
.
It has a default cache size of 1. This means it always recalculates when the value of an argument changes. However, this can be customized as needed with a specific max cache size (since 4.1.0).
It determines if an argument has changed by calling the equalityCheck
function. As lruMemoize
is designed to be used with immutable data, the default equalityCheck
function checks for changes using reference equality:
const referenceEqualityCheck = (previousValue: any, currentValue: any) => {
return previousValue === currentValue
}
Parameters
Name | Description |
---|---|
func |
The function to be memoized. |
equalityCheckOrOptions |
Either an equality check function or an options object. |
Since 4.1.0, lruMemoize
also accepts an options object as its first argument instead of an equalityCheck
function. The options
object may contain:
type EqualityFn = (a: any, b: any) => boolean
interface LruMemoizeOptions {
equalityCheck?: EqualityFn
resultEqualityCheck?: EqualityFn
maxSize?: number
}
Name | Description |
---|---|
equalityCheck |
Used to compare the individual arguments of the provided calculation function. Default = referenceEqualityCheck |
resultEqualityCheck |
If provided, used to compare a newly generated output value against previous values in the cache. If a match is found, the old value is returned. This addresses the common todos.map(todo => todo.id) use case, where an update to another field in the original data causes a recalculation due to changed references, but the output is still effectively the same. |
maxSize |
The cache size for the selector. If greater than 1, the selector will use an LRU cache internally. Default = 1 |
Warning
If resultEqualityCheck
is used inside argsMemoizeOptions
it has no effect.
Returns
A memoized function with a .clearCache()
method attached.
Type Parameters
Name | Description |
---|---|
Func |
The type of the function that is memoized. |
Examples
lruMemoize
with createSelector
Using import { shallowEqual } from 'react-redux'
import { createSelector } from 'reselect'
const selectTodoIds = createSelector(
[(state: RootState) => state.todos],
todos => todos.map(todo => todo.id),
{
memoizeOptions: {
equalityCheck: shallowEqual,
resultEqualityCheck: shallowEqual,
maxSize: 10
},
argsMemoizeOptions: {
equalityCheck: shallowEqual,
resultEqualityCheck: shallowEqual,
maxSize: 10
}
}
)
lruMemoize
with createSelectorCreator
Using import { shallowEqual } from 'react-redux'
import { createSelectorCreator, lruMemoize } from 'reselect'
const createSelectorShallowEqual = createSelectorCreator({
memoize: lruMemoize,
memoizeOptions: {
equalityCheck: shallowEqual,
resultEqualityCheck: shallowEqual,
maxSize: 10
},
argsMemoize: lruMemoize,
argsMemoizeOptions: {
equalityCheck: shallowEqual,
resultEqualityCheck: shallowEqual,
maxSize: 10
}
})
const selectTodoIds = createSelectorShallowEqual(
[(state: RootState) => state.todos],
todos => todos.map(todo => todo.id)
)
weakMapMemoize(func) - (since 5.0.0)
Description
lruMemoize
has to be explicitly configured to have a cache size larger than 1, and uses an LRU cache internally.
weakMapMemoize
creates a tree of WeakMap
-based cache nodes based on the identity of the arguments it's been called with (in this case, the extracted values from your input selectors). This allows weakMapMemoize
to have an effectively infinite cache size. Cache results will be kept in memory as long as references to the arguments still exist, and then cleared out as the arguments are garbage-collected.
Design Tradeoffs
-
Pros:
- It has an effectively infinite cache size, but you have no control over
how long values are kept in cache as it's based on garbage collection and
WeakMap
s.
- It has an effectively infinite cache size, but you have no control over
how long values are kept in cache as it's based on garbage collection and
-
Cons:
- There's currently no way to alter the argument comparisons. They're based on strict reference equality.
Use Cases
- This memoizer is likely best used for cases where you need to call the same selector instance with many different arguments, such as a single selector instance that is used in a list item component and called with item IDs like:
useSelector(state => selectSomeData(state, id))
Prior to weakMapMemoize
, you had this problem:
interface RootState {
items: { id: number; category: string; name: string }[]
}
const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category)
)
selectItemsByCategory(state, 'Electronics') // Selector runs
selectItemsByCategory(state, 'Electronics')
selectItemsByCategory(state, 'Stationery') // Selector runs
selectItemsByCategory(state, 'Electronics') // Selector runs again!
Before you could solve this in a number of different ways:
- Set the
maxSize
withlruMemoize
:
const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category),
{
memoizeOptions: {
maxSize: 10
}
}
)
But this required having to know the cache size ahead of time.
- Create unique selector instances using
useMemo
.
const makeSelectItemsByCategory = (category: string) =>
createSelector([(state: RootState) => state.items], items =>
items.filter(item => item.category === category)
)
interface Props {
category: string
}
const MyComponent: FC<Props> = ({ category }) => {
const selectItemsByCategory = useMemo(
() => makeSelectItemsByCategory(category),
[category]
)
const itemsByCategory = useSelector(selectItemsByCategory)
return (
<div>
{itemsByCategory.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
)
}
- Using
useCallback
.
const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category)
)
const MyComponent: FC<Props> = ({ category }) => {
const selectItemsByCategoryMemoized = useCallback(selectItemsByCategory, [])
const itemsByCategory = useSelector(state =>
selectItemsByCategoryMemoized(state, category)
)
return (
<div>
{itemsByCategory.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
)
}
- Use
re-reselect
:
import { createCachedSelector } from 're-reselect'
const selectItemsByCategory = createCachedSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category)
)((state: RootState, category: string) => category)
Starting in 5.0.0, you can eliminate this problem using weakMapMemoize
.
const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category),
{
memoize: weakMapMemoize,
argsMemoize: weakMapMemoize
}
)
selectItemsByCategory(state, 'Electronics') // Selector runs
selectItemsByCategory(state, 'Electronics') // Cached
selectItemsByCategory(state, 'Stationery') // Selector runs
selectItemsByCategory(state, 'Electronics') // Still cached!
This solves the problem of having to know and set the cache size prior to creating a memoized selector. Because weakMapMemoize
essentially provides a dynamic cache size out of the box.
Parameters
Name | Description |
---|---|
func |
The function to be memoized. |
Returns
A memoized function with a .clearCache()
method attached.
Type Parameters
Name | Description |
---|---|
Func |
The type of the function that is memoized. |
Examples
weakMapMemoize
with createSelector
Using import { createSelector, weakMapMemoize } from 'reselect'
const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category),
{
memoize: weakMapMemoize,
argsMemoize: weakMapMemoize
}
)
selectItemsByCategory(state, 'Electronics') // Selector runs
selectItemsByCategory(state, 'Electronics')
selectItemsByCategory(state, 'Stationery') // Selector runs
selectItemsByCategory(state, 'Electronics')
weakMapMemoize
with createSelectorCreator
Using import { createSelectorCreator, weakMapMemoize } from 'reselect'
const createSelectorWeakMap = createSelectorCreator({
memoize: weakMapMemoize,
argsMemoize: weakMapMemoize
})
const selectItemsByCategory = createSelectorWeakMap(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category)
)
selectItemsByCategory(state, 'Electronics') // Selector runs
selectItemsByCategory(state, 'Electronics')
selectItemsByCategory(state, 'Stationery') // Selector runs
selectItemsByCategory(state, 'Electronics')
unstable_autotrackMemoize(func) - (since 5.0.0)
Description
Uses an "auto-tracking" approach inspired by the work of the Ember Glimmer team. It uses a Proxy to wrap arguments and track accesses to nested fields in your selector on first read. Later, when the selector is called with new arguments, it identifies which accessed fields have changed and only recalculates the result if one or more of those accessed fields have changed. This allows it to be more precise than the shallow equality checks in lruMemoize
.
Warning
This API is still experimental and undergoing testing.
Design Tradeoffs
-
Pros:
- It is likely to avoid excess calculations and recalculate fewer times than
lruMemoize
will, which may also result in fewer component re-renders.
- It is likely to avoid excess calculations and recalculate fewer times than
-
Cons:
- It only has a cache size of 1.
- It is slower than
lruMemoize
, because it has to do more work. (How much slower is dependent on the number of accessed fields in a selector, number of calls, frequency of input changes, etc) - It can have some unexpected behavior. Because it tracks nested field accesses, cases where you don't access a field will not recalculate properly. For example, a badly-written selector like:
createSelector([state => state.todos], todos => todos)
that just immediately returns the extracted value will never update, because it doesn't see any field accesses to check.
Use Cases
- It is likely best used for cases where you need to access specific nested fields in data, and avoid recalculating if other fields in the same data objects are immutably updated.
Parameters
Name | Description |
---|---|
func |
The function to be memoized. |
Returns
A memoized function with a .clearCache()
method attached.
Type Parameters
Name | Description |
---|---|
Func |
The type of the function that is memoized. |
Examples
unstable_autotrackMemoize
with createSelector
Using import { unstable_autotrackMemoize, createSelector } from 'reselect'
const selectTodoIds = createSelector(
[(state: RootState) => state.todos],
todos => todos.map(todo => todo.id),
{ memoize: unstable_autotrackMemoize }
)
unstable_autotrackMemoize
with createSelectorCreator
Using import { unstable_autotrackMemoize, createSelectorCreator } from 'reselect'
const createSelectorAutotrack = createSelectorCreator({
memoize: unstable_autotrackMemoize
})
const selectTodoIds = createSelectorAutotrack(
[(state: RootState) => state.todos],
todos => todos.map(todo => todo.id)
)
Debugging Tools
Development-Only Checks
Reselect includes extra checks in development mode to help catch and warn about mistakes in selector behavior.
inputStabilityCheck
Due to how Cascading Memoization works in Reselect, it is crucial that your input selectors do not return a new reference on each run. If an input selector always returns a new reference, like
state => ({ a: state.a, b: state.b })
or
state => state.todos.map(todo => todo.id)
that will cause the selector to never memoize properly.
Since this is a common mistake, we've added a development mode check to catch this. By default, createSelector
will now run the input selectors twice during the first call to the selector. If the result appears to be different for the same call, it will log a warning with the arguments and the two different sets of extracted input values.
type DevModeCheckFrequency = 'always' | 'once' | 'never'
Possible Values | Description |
---|---|
once |
Run only the first time the selector is called. |
always |
Run every time the selector is called. |
never |
Never run the input stability check. |
Important
The input stability check is automatically disabled in production environments.
You can configure this behavior in two ways:
setGlobalDevModeChecks
:
1. Globally through A setGlobalDevModeChecks
function is exported from Reselect, which should be called with the desired setting.
import { setGlobalDevModeChecks } from 'reselect'
// Run only the first time the selector is called. (default)
setGlobalDevModeChecks({ inputStabilityCheck: 'once' })
// Run every time the selector is called.
setGlobalDevModeChecks({ inputStabilityCheck: 'always' })
// Never run the input stability check.
setGlobalDevModeChecks({ inputStabilityCheck: 'never' })
inputStabilityCheck
option directly to createSelector
:
2. Per selector by passing an // Create a selector that double-checks the results of input selectors every time it runs.
const selectCompletedTodosLength = createSelector(
[
// ❌ Incorrect Use Case: This input selector will not be memoized properly since it always returns a new reference.
(state: RootState) =>
state.todos.filter(({ completed }) => completed === true)
],
completedTodos => completedTodos.length,
// Will override the global setting.
{ devModeChecks: { inputStabilityCheck: 'always' } }
)
Warning
This will override the global input stability check set by calling setGlobalDevModeChecks
.
identityFunctionCheck
When working with Reselect, it's crucial to adhere to a fundamental philosophy regarding the separation of concerns between extraction and transformation logic.
-
Extraction Logic: This refers to operations like
state => state.todos
, which should be placed in input selectors. Extraction logic is responsible for retrieving or 'selecting' data from a broader state or dataset. -
Transformation Logic: In contrast, transformation logic, such as
todos => todos.map(({ id }) => id)
, belongs in the result function. This is where you manipulate, format, or transform the data extracted by the input selectors.
Most importantly, effective memoization in Reselect hinges on following these guidelines. Memoization, only functions correctly when extraction and transformation logic are properly segregated. By keeping extraction logic in input selectors and transformation logic in the result function, Reselect can efficiently determine when to reuse cached results and when to recompute them. This not only enhances performance but also ensures the consistency and predictability of your selectors.
For memoization to work as intended, it's imperative to follow both guidelines. If either is disregarded, memoization will not function properly. Consider the following example for clarity:
// ❌ Incorrect Use Case: This will not memoize correctly, and does nothing useful!
const brokenSelector = createSelector(
// ✔️ GOOD: Contains extraction logic.
[(state: RootState) => state.todos],
// ❌ BAD: Does not contain transformation logic.
todos => todos
)
type DevModeCheckFrequency = 'always' | 'once' | 'never'
Possible Values | Description |
---|---|
once |
Run only the first time the selector is called. |
always |
Run every time the selector is called. |
never |
Never run the identity function check. |
Important
The identity function check is automatically disabled in production environments.
You can configure this behavior in two ways:
setGlobalDevModeChecks
:
1. Globally through import { setGlobalDevModeChecks } from 'reselect'
// Run only the first time the selector is called. (default)
setGlobalDevModeChecks({ identityFunctionCheck: 'once' })
// Run every time the selector is called.
setGlobalDevModeChecks({ identityFunctionCheck: 'always' })
// Never run the identity function check.
setGlobalDevModeChecks({ identityFunctionCheck: 'never' })
identityFunctionCheck
option directly to createSelector
:
2. Per selector by passing an // Create a selector that checks to see if the result function is an identity function.
const selectTodos = createSelector(
[(state: RootState) => state.todos],
// This result function does not contain any transformation logic.
todos => todos,
// Will override the global setting.
{ devModeChecks: { identityFunctionCheck: 'always' } }
)
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. |
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 customizedmemoize
andargsMemoize
functions, alongside their respective options (memoizeOptions
andargsMemoizeOptions
). - The
createSelector
function now supports direct customization ofmemoize
andargsMemoize
within its options object.
- Added the ability to pass an options object to
-
Memoization Functions:
- Introduced new experimental memoization functions:
weakMapMemoize
andunstable_autotrackMemoize
. - Incorporated
memoize
andargsMemoize
into the output selector fields for debugging purposes.
- Introduced new experimental memoization functions:
-
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. - Added the
TypedStructuredSelectorCreator
utility type (currently a work-in-progress) to facilitate the creation of a pre-typed version ofcreateStructuredSelector
for your root state.
- Removed the second overload of
-
Additional Functionalities:
- Added
dependencyRecomputations
andresetDependencyRecomputations
to the output selector fields. These additions provide greater control and insight over input selectors, complementing the newargsMemoize
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.
- Added
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
andOutputParametricSelector
types. Their functionalities are now integrated intoSelector
andOutputSelector
respectively, which inherently support additional parameters.
- Removed
Optimizing Reselect
Common Mistakes
Click to expand
A somewhat common mistake is to write an input selector that extracts a value or does some derivation, and a result function that just returns its result:
// ❌ BROKEN: this will not memoize correctly, and does nothing useful!
const brokenSelector = createSelector(
[(state: RootState) => state.todos],
todos => todos
)
Any result function that just returns its inputs is incorrect! The result function should always have the transformation logic.
Similarly:
// ❌ BROKEN: this will not memoize correctly!
const brokenSelector = createSelector(
[(state: RootState) => state],
state => state.todos
)
Handling Empty Array Results
Click to expand
To reduce recalculations, use a predefined empty array when array.filter
or similar methods result in an empty array.
So you can have a pattern like this:
interface RootState {
todos: {
id: number
title: string
description: string
completed: boolean
}[]
}
const EMPTY_ARRAY: [] = []
const selectCompletedTodos = createSelector(
[(state: RootState) => state.todos],
todos => {
const completedTodos = todos.filter(todo => todo.completed === true)
return completedTodos.length === 0 ? EMPTY_ARRAY : completedTodos
}
)
Or to avoid repetition, you can create a wrapper function and reuse it:
const EMPTY_ARRAY: [] = []
export const fallbackToEmptyArray = <T>(array: T[]) => {
return array.length === 0 ? EMPTY_ARRAY : array
}
const selectCompletedTodos = createSelector(
[(state: RootState) => state.todos],
todos => {
return fallbackToEmptyArray(todos.filter(todo => todo.completed === true))
}
)
This way if the result function returns an empty array twice in a row, your component will not re-render due to a stable empty array reference:
const completedTodos = selectCompletedTodos(store.getState())
store.dispatch(addTodo())
console.log(completedTodos === selectCompletedTodos(store.getState())) //=> true
Best Practices
Click to expand
There are a few details that will help you skip running as many functions as possible and get the best possible performance out of Reselect:
- Due to the Cascading Memoization in Reselect, The first layer of checks is upon the arguments that are passed to the output selector, therefore it's best to maintain the same reference for the arguments as much as possible.
- In Redux, your state will change reference when updated. But it's best to keep the additional arguments as simple as possible, you can pass in objects or array as long as their reference does not change. Or you can pass in primitives like numbers for ids.
- Keep your input selectors as simple as possible. It's best if they mostly consist of field accessors like
state => state.todos
or argument providers like(state, id) => id
. You should not be doing any sort of calculation inside input selectors, and you should definitely not be returning an object or array with a new reference each time. - The result function is only re-run as a last resort. So make sure to put any and all calculations inside your result function. That way, Reselect will only run those calculations if all other checks fail.
This:
// ✔️ This is optimal because we have less calculations in input selectors and more in the result function.
const selectorGood = createSelector(
[(state: RootState) => state.todos],
todos => someExpensiveComputation(todos)
)
Is preferable to this:
// ❌ This is not optimal!
const selectorBad = createSelector(
[(state: RootState) => someExpensiveComputation(state.todos)],
someOtherCalculation
)
FAQ
Why isn’t my selector recomputing when the input state changes?
Check that your memoization function is compatible with your state update function (i.e. the reducer if you are using Redux). For example, a selector created with createSelector
will not work with a state update function that mutates an existing object instead of creating a new one each time. createSelector
uses an identity check (===
) to detect that an input has changed, so mutating an existing object will not trigger the selector to recompute because mutating an object does not change its identity. Note that if you are using Redux, mutating the state object is almost certainly a mistake.
Why is my selector recomputing when the input state stays the same?
To address unexpected recomputations in your selector, first ensure that inputStabilityCheck
is set to either 'always'
or 'once'
. This setting aids in debugging by monitoring the stability of your inputs. Additionally, utilize output selector fields such as recomputations
, resetRecomputations
, dependencyRecomputations
, and resetDependencyRecomputations
. These tools help identify the source of the issue.
Keep an eye on the dependencyRecomputations
count. If it increases while recomputations
remains the same, it suggests that your arguments are changing references but your input selectors are stable which is typically the desired behavior.
Detailed Explanation: Selector Recomputations
To delve deeper, you can determine which arguments are changing references too frequently by using the argsMemoizeOptions
and equalityCheck
. Consider the following example:
interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean; type: string }[]
}
const selectAlertsByType = createSelector(
[
(state: RootState) => state.alerts,
(state: RootState, type: string) => type
],
(alerts, type) => alerts.filter(todo => todo.type === type),
{
argsMemoizeOptions: {
// This will check the arguments passed to the output selector.
equalityCheck: (a, b) => {
if (a !== b) {
console.log('Changed argument:', a, 'to', b)
}
return a === b
}
}
}
)
Redux?
Can I use Reselect withoutYes. Reselect has no dependencies on any other package, so although it was designed to be used with Redux it can be used independently. It can be used with any plain JS data, such as typical React state values, as long as that data is being updated immutably.
How do I create a selector that takes an argument?
Each of the input selectors you provide will be called with all of the selector's arguments. You can add additional input selectors to extract arguments and forward them to the result function, like this:
const selectTodosByCategory = createSelector(
(state: RootState) => state.todos,
// Extract the second argument to pass it on
(state: RootState, category: string) => category,
(todos, category) => todos.filter(t => t.category === category)
)
Detailed Explanation: Selectors and Arguments
When creating a selector that accepts arguments in Reselect, it's important to structure your input and output selectors appropriately. Here are key points to consider:
-
Consistency in Arguments: Ensure that all positional arguments across input selectors are of the same type for consistency.
-
Selective Argument Usage: Design each selector to use only its relevant argument(s) and ignore the rest. This is crucial because all input selectors receive the same arguments that are passed to the output selector.
Suppose we have the following state structure:
interface RootState {
items: {
id: number
category: string
vendor: { id: number; name: string }
}[]
// ... other state properties ...
}
To create a selector that filters items
based on a category
and excludes a specific id
, you can set up your selectors as follows:
const selectAvailableItems = createSelector(
[
// First input selector extracts items from the state
(state: RootState) => state.items,
// Second input selector forwards the category argument
(state: RootState, category: string) => category,
// Third input selector forwards the ID argument
(state: RootState, category: string, id: number) => id
],
// Output selector uses the extracted items, category, and ID
(items, category, id) =>
items.filter(item => item.category === category && item.id !== id)
)
Internally Reselect is doing this:
// Input selector #1
const items = (state: RootState, category: string, id: number) => state.items
// Input selector #2
const category = (state: RootState, category: string, id: number) => category
// Input selector #3
const id = (state: RootState, category: string, id: number) => id
// result of output selector
const finalResult =
// The result function
items.filter(item => item.category === category && item.id !== id)
In this example, selectItemId
expects that its second argument will be some simple value, while selectVendorName
expects that the second argument is an object. If you call selectItemById(state, 42)
, selectVendorName
will break because it's trying to access 42.name
. Reselect's TS types should detect this and prevent compilation:
const selectItems = (state: RootState) => state.items
// expects a number as the second argument
const selectItemId = (state: RootState, itemId: number) => itemId
// expects an object as the second argument
const selectVendorName = (
state: RootState,
vendor: { id: number; name: string }
) => vendor.name
const selectItemById = createSelector(
[selectItems, selectItemId, selectVendorName],
(items, itemId, vendorName) => items[itemId]
)
Can the memoization behavior be customized?
Yes. The built-in lruMemoize
memoizer works great for a lot of use cases, but it can be customized or swapped out for a different memoizer. See these examples.
How do I test a selector?
Selectors are pure functions - for a given input, a selector should always produce the same result. For this reason they are simple to unit test: call the selector with a set of inputs, and assert that the result value matches an expected shape.
Detailed Explanation: Testing Selectors
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 }
]
}
// With `Vitest` or `Jest`
test('selector unit test', () => {
const selectTodoIds = createSelector(
[(state: RootState) => state.todos],
todos => todos.map(({ id }) => id)
)
const firstResult = selectTodoIds(state)
const secondResult = selectTodoIds(state)
// Reference equality should pass.
expect(firstResult).toBe(secondResult)
// Deep equality should also pass.
expect(firstResult).toStrictEqual(secondResult)
selectTodoIds(state)
selectTodoIds(state)
selectTodoIds(state)
// The result function should not recalculate.
expect(selectTodoIds.recomputations()).toBe(1)
// input selectors should not recalculate.
expect(selectTodoIds.dependencyRecomputations()).toBe(1)
})
// With `Chai`
test('selector unit test', () => {
const selectTodoIds = createSelector(
[(state: RootState) => state.todos],
todos => todos.map(({ id }) => id)
)
const firstResult = selectTodoIds(state)
const secondResult = selectTodoIds(state)
// Reference equality should pass.
expect(firstResult).to.equal(secondResult)
// Deep equality should also pass.
expect(firstResult).to.deep.equal(secondResult)
selectTodoIds(state)
selectTodoIds(state)
selectTodoIds(state)
// The result function should not recalculate.
expect(selectTodoIds.recomputations()).to.equal(1)
// input selectors should not recalculate.
expect(selectTodoIds.dependencyRecomputations()).to.equal(1)
})
Can I share a selector across multiple component instances?
Yes, although if they pass in different arguments, you will need to handle that in order for memoization to work consistently:
- Pass a larger
maxSize
if usinglruMemoize
( as of 4.1.0+) - Use
weakMapMemoize
(as of 5.0.0+)
Are there TypeScript Typings?
Yes! Reselect is now written in TypeScript itself, so they should Just Work™.
Type instantiation is excessively deep and possibly infinite
I am seeing a TypeScript error: Starting in 5.0.0 you should be able to nest up to 30 selectors, but in case you still run into this issue, you can refer to this comment for a discussion of the problem, as relating to nested selectors.
curried selector?
How can I make aSelectors that take arguments are commonly used inside of React-Redux's useSelector
by using a closure to pass along the extra arguments:
function TodosList({ category }) {
const filteredTodos = useSelector(state =>
selectTodosByCategory(state, category)
)
}
If you prefer to use a curried form instead, you can create a curried selector with this recipe:
Detailed Explanation: Creating Curried Selectors
const currySelector = <
State,
Result,
Params extends readonly any[],
AdditionalFields
>(
selector: ((state: State, ...args: Params) => Result) & AdditionalFields
) => {
const curriedSelector = (...args: Params) => {
return (state: State) => {
return selector(state, ...args)
}
}
return Object.assign(curriedSelector, selector)
}
const selectTodoByIdCurried = currySelector(
createSelector(
[(state: RootState) => state.todos, (state: RootState, id: number) => id],
(todos, id) => todos.find(todo => todo.id === id)
)
)
Or for reusability you can do this:
import type { weakMapMemoize, SelectorArray, UnknownMemoizer } from 'reselect'
import { createSelector } from 'reselect'
export const createCurriedSelector = <
InputSelectors extends SelectorArray,
Result,
OverrideMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
OverrideArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
>(
...args: Parameters<
typeof createSelector<
InputSelectors,
Result,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
>
>
) => {
return currySelector(createSelector(...args))
}
This:
const selectTodoById = createSelector(
[(state: RootState) => state.todos, (state: RootState, id: number) => id],
(todos, id) => todos.find(todo => todo.id === id)
)
selectTodoById(state, 0)
Is the same as this:
selectTodoByIdCurried(0)(state)
As before you had to do this:
const todoById = useSelector(state => selectTodoById(state, id))
Now you can do this:
const todoById = useSelector(selectTodoByIdCurried(id))
Another thing you can do if you are using React-Redux is create a custom hook factory function:
import { useSelector } from 'react-redux'
export const createParametricSelectorHook = <
Result,
Params extends readonly unknown[]
>(
selector: (state: RootState, ...params: Params) => Result
) => {
return (...args: Params) => {
return useSelector(state => selector(state, ...args))
}
}
const useSelectTodo = createParametricSelectorHook(selectTodoById)
And then inside your component:
import type { FC } from 'react'
interface Props {
id: number
}
const MyComponent: FC<Props> = ({ id }) => {
const todo = useSelectTodo(id)
return <div>{todo.title}</div>
}
createSelector
for my root state?
How can I make pre-typed version of When used with Redux, it's typical to have all input selectors take (state: RootState)
as their first argument. Creating a pre-typed version of createSelector
can shorten that repetition.
Detailed Explanation: Pre-Typed `createSelector`
You can create a custom typed version of createSelector
by defining a utility type that extends the original createSelector
function. Here's an example:
import type {
OutputSelector,
Selector,
SelectorArray,
UnknownMemoizer,
weakMapMemoize
} from 'reselect'
import { createSelector } from 'reselect'
interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean }[]
}
export type TypedCreateSelector<
State,
MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
> = <
InputSelectors extends readonly Selector<State>[],
Result,
OverrideMemoizeFunction extends UnknownMemoizer = MemoizeFunction,
OverrideArgsMemoizeFunction extends UnknownMemoizer = ArgsMemoizeFunction
>(
...createSelectorArgs: Parameters<
typeof createSelector<
InputSelectors,
Result,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
>
>
) => ReturnType<
typeof createSelector<
InputSelectors,
Result,
OverrideMemoizeFunction,
OverrideArgsMemoizeFunction
>
>
export const createAppSelector: TypedCreateSelector<RootState> = createSelector
[!WARNING]: This approach currently only supports input selectors provided as a single array.
createSelector
without memoization?
What if I want to use There may be rare cases when you might want to use createSelector
for its composition syntax, but without any memoization applied. In that case, create an identity function
and use it as the memoizers:
const identity = <Func extends (...args: any[]) => any>(func: Func) => func
const createNonMemoizedSelector = createSelectorCreator({
memoize: identity,
argsMemoize: identity
})
External References
Click to expand
Related Projects
re-reselect
Enhances Reselect selectors by wrapping createSelector
and returning a memoized collection of selectors indexed with the cache key returned by a custom resolver function.
Useful to reduce selectors recalculation when the same selector is repeatedly called with one/few different arguments.
reselect-tools
- Measure selector recomputations across the app and identify performance bottlenecks
- Check selector dependencies, inputs, outputs, and recomputations at any time with the chrome extension
- Statically export a JSON representation of your selector graph for further analysis
reselect-debugger
Flipper plugin and the connect app for debugging selectors in React Native Apps.
Inspired by Reselect Tools, so it also has all functionality from this library and more, but only for React Native and Flipper.
- Selectors Recomputations count in live time across the App for identify performance bottlenecks
- Highlight most recomputed selectors
- Dependency Graph
- Search by Selectors Graph
- Selectors Inputs
- Selectors Output (In case if selector not dependent from external arguments)
- Shows "Not Memoized (NM)" selectors
License
MIT
Prior Art and Inspiration
Click to expand
Originally inspired by getters in NuclearJS, subscriptions in re-frame and this proposal from speedskater.