Technical Documentation: reduxjs/reselect
βΉοΈ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference
Overview (`README.md`)
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
# pnpm
pnpm add reselectDocumentation
The Reselect docs are available at https://reselect.js.org, and include usage guides and API references:
- Introduction
- How Does Reselect Work?
- API Reference:
- FAQ
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)
) //=> trueAs 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
)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 customizedmemoizeandargsMemoizefunctions, alongside their respective options (memoizeOptionsandargsMemoizeOptions). - The
createSelectorfunction now supports direct customization ofmemoizeandargsMemoizewithin its options object.
- Added the ability to pass an options object to
Memoization Functions:
- Introduced a new default memoization function:
weakMapMemoize - Incorporated
memoizeandargsMemoizeinto the output selector fields for debugging purposes.
- Introduced a new default memoization function:
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 infiniteerror.
Selector API Enhancements:
- Removed the second overload of
createStructuredSelectordue to its susceptibility to runtime errors.
- Removed the second overload of
Additional Functionalities:
- Added
dependencyRecomputationsandresetDependencyRecomputationsto the output selector fields. These additions provide greater control and insight over input selectors, complementing the newargsMemoizeAPI. - 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
ParametricSelectorandOutputParametricSelectortypes. Their functionalities are now integrated intoSelectorandOutputSelectorrespectively, which inherently support additional parameters.
- Removed
License
MIT
References
Click to Expand
Originally inspired by getters in NuclearJS, subscriptions in re-frame and this proposal from speedskater.
Overview (`docs/README.md`)
Reselect docs
The pages in this folder are published as part of the combined Redux docs site, at https://redux.js.org/reselect. The site itself (Docusaurus config, theme, search, redirects, build scripts) lives in the reduxjs/redux repo under website/. This folder holds only the Reselect pages.
What lives where
| What | Where |
|---|---|
| Reselect pages | docs/**/*.{md,mdx} in this repo |
| Reselect sidebar | docs/sidebars.ts in this repo |
| Code examples shown in the pages | docs/examples/ in this repo |
| Images used by these pages | docs/assets/, referenced by relative path |
| Tutorials, TypeScript setup, style guide, FAQ, troubleshooting | docs/ in reduxjs/redux |
Site config, navbar, theme, search, redirects (website/_redirects) |
website/ in reduxjs/redux |
Topics shared by all the Redux libraries are written once, in the core docs. Deriving data with selectors is covered in the core usage guide at /usage/deriving-data-selectors. Link to those pages instead of repeating them here.
Links
- Other Reselect pages: a relative file link (
../api/createSelector.mdx) or a site path (/reselect/api/createSelector). - Other libraries and the core docs: site paths, such as
/usage/deriving-data-selectors,/toolkit/api/createSelector, or/react-redux/api/hooks. Do not use fullhttps://redux.js.org/...URLs. - When you rename, move, or delete a page, add a redirect for the old URL to
website/_redirectsinreduxjs/redux.
Sidebar
New pages only appear in the navigation once they are listed in docs/sidebars.ts. The file has no imports because the site loads it from a copy of this folder. Its local SidebarItem type catches misspelled keys:
pnpm exec tsc -p docs/tsconfig.json --noEmitThe site build checks that every doc id in the sidebar exists.
Code examples
The TypeScript/JavaScript tabs in the pages are generated from the files in docs/examples/, which are type-checked against src/. To add or change one:
- Edit or add the file under
docs/examples/. - In the page, add a
{/* START: path/to/example.ts */}line and a matching{/* END: path/to/example.ts */}line where the tabs should go. The path is relative todocs/examples/. - Run
pnpm docs:examples. It compiles the examples to JavaScript, writes both versions into the pages between the markers, and formats the result. - Commit the updated pages. The site does not run this script; it publishes the pages as committed.
Previewing a PR
Every PR that changes docs/ gets a Netlify deploy preview of the whole combined site, with this branch's Reselect docs in place of the published ones.
Previewing locally
Clone reduxjs/redux next to this repo and install the site's dependencies once:
git clone https://github.com/reduxjs/redux.git ../redux
cd ../redux/docs && pnpm install
cd ../website && pnpm installThen start the dev server from ../redux/website, pointing it at this checkout:
DOCS_SOURCE_RESELECT=../../reselect pnpm devpnpm dev copies this repo's docs into website/external/reselect, copies each file again when it changes, and runs docusaurus start. The other libraries are cloned from GitHub. Set DOCS_SOURCE_REDUX_TOOLKIT or DOCS_SOURCE_REACT_REDUX as well to use local checkouts of those.
The dev server does not report broken links. To run the same checks as a deploy preview:
DOCS_SOURCE_RESELECT=../../reselect pnpm fetch-docs --force
pnpm buildIntroduction Getting Started (`docs/introduction/getting-started.mdx`)
id: getting-started title: Getting Started sidebar_label: Getting Started sidebar_position: 1 hide_title: true
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
import Link from '@docusaurus/Link'
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.
If you are using Reselect in a Redux app, start with the Redux docs usage page on Deriving Data with Selectors. It covers the purpose and motivation for selectors, why memoized selectors are useful, typical Reselect usage patterns, and using selectors with React-Redux. These Reselect docs are the reference for the library itself: how the memoization works, the full API, and the FAQ.
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 reselectBasic 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:
{/* START: basicUsage.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
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)
) //=> trueimport { 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{/* END: basicUsage.ts */}
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 selector 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
)FAQ (`docs/FAQ.mdx`)
id: FAQ title: FAQ sidebar_label: FAQ hide_title: true
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
import Link from '@docusaurus/Link'
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.
To delve deeper, you can determine which arguments are changing references too frequently by using the argsMemoizeOptions and equalityCheck. Consider the following example:
{/* START: FAQ/selectorRecomputing.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector, lruMemoize } from 'reselect'
export 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),
{
argsMemoize: lruMemoize,
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
}
}
}
)import { createSelector, lruMemoize } from 'reselect'
const selectAlertsByType = createSelector(
[state => state.alerts, (state, type) => type],
(alerts, type) => alerts.filter(todo => todo.type === type),
{
argsMemoize: lruMemoize,
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
}
}
}
){/* END: FAQ/selectorRecomputing.ts */}
Can I use Reselect without Redux?
Yes. 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)
)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.
{/* START: FAQ/howToTest.test.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */{/* END: FAQ/howToTest.test.ts */}
:::note Resetting a selector's cache between tests
Because Reselect uses a two-stage "cascading" memoization, a memoized selector holds two separate caches:
- the
argsMemoizecache for the arguments passed to the output selector, attached to the selector itself. - the
memoizecache for the results of the input selectors, attached tomemoizedResultFunc.
Calling clearCache on the selector only clears the first one. To fully reset a selector between tests, clear both:
selector.clearCache() // clears the `argsMemoize` cache
selector.memoizedResultFunc.clearCache() // clears the `memoize` cache
selector.resetRecomputations()
selector.resetDependencyRecomputations()Alternatively, create the selector inside each test (or in a beforeEach block) so that every test starts with a fresh cache.
:::
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
maxSizeif usinglruMemoize( as of 4.1.0+) - Use
weakMapMemoize(as of 5.0.0+). If the arguments are ever-changing primitives, pass itsmaxSizeoption to bound the cache growth (as of 5.3.0+)
Also note that if the shared selector composes an expensive selector that only depends on the state, the varying arguments will fan out that inner selector's cache and make it re-check its inputs once per distinct argument on every state update. See Keeping extra arguments away from unary input selectors.
Are there TypeScript Typings?
Yes! Reselect is now written in TypeScript itself, so they should Just Workβ’.
I am seeing a TypeScript error: `Type instantiation is excessively deep and possibly infinite`
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.
How can I make a curried selector?
Selectors 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:
{/* START: FAQ/currySelector.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector } from 'reselect'
import type { RootState } from './selectorRecomputing'
export 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)
)
)import { createSelector } from 'reselect'
export const currySelector = selector => {
const curriedSelector = (...args) => {
return state => {
return selector(state, ...args)
}
}
return Object.assign(curriedSelector, selector)
}
const selectTodoByIdCurried = currySelector(
createSelector([state => state.todos, (state, id) => id], (todos, id) =>
todos.find(todo => todo.id === id)
)
){/* END: FAQ/currySelector.ts */}
Or for reusability you can do this:
{/* START: FAQ/createCurriedSelector.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import type { weakMapMemoize, SelectorArray, UnknownMemoizer } from 'reselect'
import { createSelector } from 'reselect'
import { currySelector } from './currySelector'
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))
}import { createSelector } from 'reselect'
import { currySelector } from './currySelector'
export const createCurriedSelector = (...args) => {
return currySelector(createSelector(...args))
}{/* END: FAQ/createCurriedSelector.ts */}
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:
{/* START: FAQ/createParametricSelectorHook.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
interface RootState {
todos: {
id: number
completed: boolean
title: string
description: string
}[]
alerts: { id: number; read: boolean }[]
}
const state: RootState = {
todos: [
{
id: 0,
completed: false,
title: 'Figure out if plants are really plotting world domination.',
description: 'They may be.'
},
{
id: 1,
completed: true,
title: 'Practice telekinesis for 15 minutes',
description: 'Just do it'
}
],
alerts: [
{ id: 0, read: false },
{ id: 1, read: true }
]
}
const selectTodoById = createSelector(
[(state: RootState) => state.todos, (state: RootState, id: number) => id],
(todos, id) => todos.find(todo => todo.id === id)
)
export const createParametricSelectorHook = <
Result,
Params extends readonly unknown[]
>(
selector: (state: RootState, ...params: Params) => Result
) => {
return (...args: Params) => {
return useSelector((state: RootState) => selector(state, ...args))
}
}
export const useSelectTodo = createParametricSelectorHook(selectTodoById)import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
const state = {
todos: [
{
id: 0,
completed: false,
title: 'Figure out if plants are really plotting world domination.',
description: 'They may be.'
},
{
id: 1,
completed: true,
title: 'Practice telekinesis for 15 minutes',
description: 'Just do it'
}
],
alerts: [
{ id: 0, read: false },
{ id: 1, read: true }
]
}
const selectTodoById = createSelector(
[state => state.todos, (state, id) => id],
(todos, id) => todos.find(todo => todo.id === id)
)
export const createParametricSelectorHook = selector => {
return (...args) => {
return useSelector(state => selector(state, ...args))
}
}
export const useSelectTodo = createParametricSelectorHook(selectTodoById){/* END: FAQ/createParametricSelectorHook.ts */}
And then inside your component:
{/* START: FAQ/MyComponent.tsx */}
<Tabs
groupId='language'
defaultValue='tsx'
values={[
{label: 'TypeScript', value: 'tsx'},
{label: 'JavaScript', value: 'jsx'},
]}>
import type { FC } from 'react'
import { useSelectTodo } from './createParametricSelectorHook'
interface Props {
id: number
}
const MyComponent: FC<Props> = ({ id }) => {
const todo = useSelectTodo(id)
return <div>{todo?.title}</div>
}import { useSelectTodo } from './createParametricSelectorHook'
const MyComponent = ({ id }) => {
const todo = useSelectTodo(id)
return <div>{todo?.title}</div>
}{/* END: FAQ/MyComponent.tsx */}
How can I make a pre-typed version of `createSelector` for my root state?
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.
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
} 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.
:::
What if I want to use `createSelector` without memoization?
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:
{/* START: FAQ/identity.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelectorCreator } from 'reselect'
const identity = <Func extends (...args: any[]) => any>(func: Func) => func
const createNonMemoizedSelector = createSelectorCreator({
memoize: identity,
argsMemoize: identity
})import { createSelectorCreator } from 'reselect'
const identity = func => func
const createNonMemoizedSelector = createSelectorCreator({
memoize: identity,
argsMemoize: identity
}){/* END: FAQ/identity.ts */}
External References (`docs/external-references.mdx`)
id: external-references title: External References sidebar_label: External References hide_title: true
External References
WeakMap- Strict equality (reference equality check)
- Memoization
- Identity function
useMemo- Re-reselect
- Redux
- React
- React-Redux
- Redux Toolkit
Related Projects (`docs/related-projects.mdx`)
id: related-projects title: Related Projects sidebar_label: Related Projects hide_title: true
import Link from '@docusaurus/Link'
Related Projects
[Re-reselect](https://github.com/toomuchdesign/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
Tsconfig.Json (`docs/tsconfig.json`)
{
"compilerOptions": {
"strict": true,
"noEmit": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"types": []
},
"files": ["sidebars.ts"]
}
Api CreateSelector (`docs/api/createSelector.mdx`)
id: createSelector title: createSelector sidebar_label: createSelector hide_title: true
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
import Link from '@docusaurus/Link'
`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. |
:::note Clearing the cache
Both the output selector and its memoizedResultFunc expose the fields attached by their memoize function β for the built-in memoizers that means clearCache, resultsCount and resetResultsCount.
Because a selector keeps a separate cache for its arguments (argsMemoize) and for its result (memoize), fully clearing it requires calling clearCache on both:
mySelector.clearCache() // clears the `argsMemoize` cache
mySelector.memoizedResultFunc.clearCache() // clears the `memoize` cache:::
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>():
{/* START: createSelector/withTypes.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
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
// highlight-start
state => state.todos
// highlight-end
],
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
// highlight-start
state => state.todos
// highlight-end
],
todos => todos.map(({ id }) => id)
){/* END: createSelector/withTypes.ts */}
Import and use the pre-typed createAppSelector instead of the original, and the type for state will be used automatically.
:::danger Known Limitations
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:
{/* START: createSelector/annotateResultFunction.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
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.
// highlight-start
(todos: Todo[]) => todos.map(({ id }) => id)
// highlight-end
)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.
// highlight-start
todos => todos.map(({ id }) => id)
// highlight-end
){/* END: createSelector/annotateResultFunction.ts */}
:::
:::tip
You can also use this API with createSelectorCreator to create a pre-typed custom selector creator:
{/* START: createSelector/createAppSelector.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
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
// highlight-start
state => state.alerts
// highlight-end
],
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
// highlight-start
state => state.alerts
// highlight-end
],
alerts => alerts.filter(({ read }) => read)
){/* END: createSelector/createAppSelector.ts */}
:::
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.
:::info Default Memoization
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.
Api CreateSelectorCreator (`docs/api/createSelectorCreator.mdx`)
id: createSelectorCreator title: createSelectorCreator sidebar_label: createSelectorCreator hide_title: true
`createSelectorCreator`
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.
API Reference
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 = lruMemoize before 5.0.0 and weakMapMemoize after |
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.inputStabilityCheck? |
Overrides the global input stability check for the selector. Possible values are: 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. Default = 'once' 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 (legacy)
| 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. |
Usage Guide
Examples
Using `options` (since 5.0.0)
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`
)Using `memoize` and `...memoizeOptions`
createSelectorCreator can be used to make a customized version of createSelector.
The memoize argument is a memoization function to replace the default configured memoizer (normally 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)Customize `equalityCheck` for `lruMemoize`
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
)Api CreateStructuredSelector (`docs/api/createStructuredSelector.mdx`)
id: createStructuredSelector title: createStructuredSelector sidebar_label: createStructuredSelector hide_title: true
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
`createStructuredSelector`
A convenience function that simplifies returning an object made up of selector results.
API Reference
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 memoize function that is used to memoize the arguments passed into the generated structured selector. It defaults to weakMapMemoize. |
Usage Guide
Examples
Modern Use Case
{/* START: createStructuredSelector/modernUseCase.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector, createStructuredSelector } from 'reselect'
export interface RootState {
todos: {
id: number
completed: boolean
title: string
description: string
}[]
alerts: { id: number; read: boolean }[]
}
// This:
export 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:
export 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
}
}
)import { createSelector, createStructuredSelector } from 'reselect'
// This:
export const structuredSelector = createStructuredSelector(
{
todos: state => state.todos,
alerts: state => state.alerts,
todoById: (state, id) => state.todos[id]
},
createSelector
)
// Is essentially the same as this:
export const selector = createSelector(
[state => state.todos, state => state.alerts, (state, id) => state.todos[id]],
(todos, alerts, todoById) => {
return {
todos,
alerts,
todoById
}
}
){/* END: createStructuredSelector/modernUseCase.ts */}
In your component:
{/* START: createStructuredSelector/MyComponent.tsx */}
<Tabs
groupId='language'
defaultValue='tsx'
values={[
{label: 'TypeScript', value: 'tsx'},
{label: 'JavaScript', value: 'jsx'},
]}>
import type { RootState } from 'createStructuredSelector/modernUseCase'
import { structuredSelector } from 'createStructuredSelector/modernUseCase'
import type { FC } from 'react'
import { useSelector } from 'react-redux'
interface Props {
id: number
}
const MyComponent: FC<Props> = ({ id }) => {
const { todos, alerts, todoById } = useSelector((state: RootState) =>
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>
)
}import { structuredSelector } from 'createStructuredSelector/modernUseCase'
import { useSelector } from 'react-redux'
const MyComponent = ({ 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>
)
}{/* END: createStructuredSelector/MyComponent.tsx */}
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) => ({
x: a,
y: 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
})
})Defining a Pre-Typed `createStructuredSelector`
As of Reselect 5.1.0, you can create a "pre-typed" version of createStructuredSelector where the state type is predefined. This allows you to set the state type once, eliminating the need to specify it with every createStructuredSelector call.
To do this, you can call createStructuredSelector.withTypes<StateType>():
{/* START: createStructuredSelector/withTypes.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createStructuredSelector } from 'reselect'
export interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean }[]
}
export const createStructuredAppSelector =
createStructuredSelector.withTypes<RootState>()
const structuredAppSelector = createStructuredAppSelector({
// Type of `state` is set to `RootState`, no need to manually set the type
// highlight-start
todos: state => state.todos,
// highlight-end
alerts: state => state.alerts,
todoById: (state, id: number) => state.todos[id]
})import { createStructuredSelector } from 'reselect'
export const createStructuredAppSelector = createStructuredSelector.withTypes()
const structuredAppSelector = createStructuredAppSelector({
// Type of `state` is set to `RootState`, no need to manually set the type
// highlight-start
todos: state => state.todos,
// highlight-end
alerts: state => state.alerts,
todoById: (state, id) => state.todos[id]
}){/* END: createStructuredSelector/withTypes.ts */}
Api Development Only Checks (`docs/api/development-only-checks.mdx`)
id: development-only-checks title: Development-Only Checks sidebar_label: Development-Only Checks sidebar_position: 1 hide_title: true
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Development-Only Checks
In development mode, Reselect runs extra checks on your selectors and warns about common mistakes:
| Check | What it catches |
|---|---|
inputStabilityCheck |
input selectors that return a new reference on every run, which breaks memoization |
identityFunctionCheck |
Result functions that return their argument unchanged, which memoize nothing useful |
cacheSizeCheck |
weakMapMemoize caches that grow without bound from ever-changing primitive arguments |
:::info
All development-only checks are automatically disabled in production environments.
:::
Configuring the checks
Every check runs with one of three frequencies:
type DevModeCheckFrequency = 'always' | 'once' | 'never'| Possible Values | Description |
|---|---|
once |
Run the check once per selector: on the first call for the stability checks, or the first time the threshold is crossed for cacheSizeCheck. |
always |
Run the check on every call. |
never |
Never run the check. |
The default for every check is once: catching a mistake does not require checking on every call, so the checks stay cheap.
Globally with `setGlobalDevModeChecks`
The setGlobalDevModeChecks function changes the frequency for all selectors. It takes a partial object, so set only the checks you want to change:
import { setGlobalDevModeChecks } from 'reselect'
// Run the input stability check every time any selector is called.
setGlobalDevModeChecks({ inputStabilityCheck: 'always' })
// Turn off the identity function check entirely.
setGlobalDevModeChecks({ identityFunctionCheck: 'never' })Per selector with the `devModeChecks` option
Individual selectors can override the global setting by passing a devModeChecks option to createSelector:
const selectTodoIds = createSelector(
[(state: RootState) => state.todos],
todos => todos.map(todo => todo.id),
{
devModeChecks: {
inputStabilityCheck: 'always',
identityFunctionCheck: 'never'
}
}
):::warning
A devModeChecks option always overrides the global setting for that selector, in both directions: it can turn a check on for one selector after globally disabling it, or turn it off for one selector that is a known false positive.
:::
:::caution
cacheSizeCheck can only be configured globally. It runs inside weakMapMemoize itself, which never sees createSelector's options, so passing devModeChecks: { cacheSizeCheck: ... } to a selector has no effect.
:::
`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)the selector will never memoize properly.
Since this is a common mistake, createSelector runs the input selectors twice during the first call to the selector. If the results appear to be different for the same call, it logs a warning with the arguments and the two different sets of extracted input values.
Turning the check up to always is useful while debugging a selector that recomputes more than expected:
{/* START: development-only-checks/inputStabilityCheck.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector } from 'reselect'
interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean }[]
}
// 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' } }
)import { createSelector } from 'reselect'
// 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 => state.todos.filter(({ completed }) => completed === true)
],
completedTodos => completedTodos.length,
// Will override the global setting.
{ devModeChecks: { inputStabilityCheck: 'always' } }
){/* END: development-only-checks/inputStabilityCheck.ts */}
`identityFunctionCheck`
Reselect relies on a separation of concerns between extraction and transformation logic:
- Extraction logic retrieves data from a broader state, like
state => state.todos. It belongs in input selectors. - Transformation logic manipulates or formats that data, like
todos => todos.map(({ id }) => id). It belongs in the result function.
Memoization only works when the two are properly separated: the input selector results are what get compared between calls, and the result function is what gets skipped on a cache hit. A result function that returns its argument unchanged means the selector does nothing useful:
// β 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
)This check detects that pattern and logs a warning the first time the selector runs:
{/* START: development-only-checks/identityFunctionCheck.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector } from 'reselect'
interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean }[]
}
// Create a selector that checks to see if the result function is an identity function.
const selectTodos = createSelector(
// βοΈ GOOD: Contains extraction logic.
[(state: RootState) => state.todos],
// β BAD: Does not contain transformation logic.
todos => todos,
// Will override the global setting.
{ devModeChecks: { identityFunctionCheck: 'always' } }
)import { createSelector } from 'reselect'
// Create a selector that checks to see if the result function is an identity function.
const selectTodos = createSelector(
// βοΈ GOOD: Contains extraction logic.
[state => state.todos],
// β BAD: Does not contain transformation logic.
todos => todos,
// Will override the global setting.
{ devModeChecks: { identityFunctionCheck: 'always' } }
){/* END: development-only-checks/identityFunctionCheck.ts */}
`cacheSizeCheck`
weakMapMemoize (the default memoizer since 5.0) keys its cache
tree by argument identity, and the two kinds of arguments age out of the cache
very differently:
- Results keyed by an object or function argument live in a
WeakMap. Once that argument is garbage-collected, the cached result goes with it. - Results keyed by a primitive argument (a string, number, or boolean) live in a regular
Map. They are held strongly and are only released by calling.clearCache()on the memoized function.
A selector that keeps seeing new primitive values in the same argument position β ids, offsets, timestamps, page numbers β therefore grows its cache without bound. A paginated selector is the canonical case:
const selectVisibleItems = createSelector(
[
(state: RootState) => state.items,
(state: RootState, from: number) => from,
(state: RootState, to: number) => to
],
(items, from, to) => items.slice(from, to)
)As the user scrolls, every (from, to) pair ever passed stays cached: the sliced arrays for (0, 10), (1, 11), (2, 12), and so on are all retained for as long as state.items is alive, long after those windows scrolled out of view.
This check counts the distinct primitive values cached for each argument position. When a single position exceeds 1,000 distinct values, it logs a warning naming the memoized function. The count is per argument position, so a per-item selector called with a bounded set of ids will not trip it β the warning fires on the unbounded pattern, not on legitimate wide caches.
If you see this warning, the usual fixes are:
- Pass the
maxSizeoption to bound the cache (as of 5.3.0). Note that bounding a selector created bycreateSelectorrequiresmaxSizein bothmemoizeOptionsandargsMemoizeOptions. - Switch that selector to
lruMemoize, which holds a fixed number of results with exact LRU eviction. - Call
.clearCache()at a suitable point, such as when the data it derives from is replaced. Clearing the cache also re-arms the warning, so you will hear about it again if the cache regrows.
Unlike the other checks, cacheSizeCheck is configured globally only (see the caution above):
import { setGlobalDevModeChecks } from 'reselect'
// Warn once per memoized function. (default)
setGlobalDevModeChecks({ cacheSizeCheck: 'once' })
// Warn on every cache insertion past the threshold.
setGlobalDevModeChecks({ cacheSizeCheck: 'always' })
// Never check cache sizes.
setGlobalDevModeChecks({ cacheSizeCheck: 'never' })Api LruMemoize (`docs/api/lruMemoize.mdx`)
id: lruMemoize title: lruMemoize sidebar_label: lruMemoize hide_title: true
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
`lruMemoize`
A memoization function that uses a provided equality check function to compare its inputs. This was originally known as defaultMemoize, and was the default inside of createSelector up through version 4.x.
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:
{/* START: lruMemoize/referenceEqualityCheck.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
const referenceEqualityCheck = (previousValue: any, currentValue: any) => {
return previousValue === currentValue
}const referenceEqualityCheck = (previousValue, currentValue) => {
return previousValue === currentValue
}{/* END: lruMemoize/referenceEqualityCheck.ts */}
API Reference
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<T = any> = (a: T, b: T) => boolean
interface LruMemoizeOptions<Result = any> {
equalityCheck?: EqualityFn
resultEqualityCheck?: EqualityFn<Result>
maxSize?: number
}| Name | Description |
|---|---|
equalityCheck |
Used to compare the individual arguments of the provided calculation function. Default = defaultEqualityCheck |
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 As of 5.3.0, weakMapMemoize also accepts a maxSize option. The difference: lruMemoize keeps an exact bound with per-entry LRU eviction, while weakMapMemoize uses a cheaper generational bound of roughly 2 * maxSize with batch eviction. |
:::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. |
Usage Guide
Examples
Using `lruMemoize` with `createSelector`
{/* START: lruMemoize/usingWithCreateSelector.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { shallowEqual } from 'react-redux'
import { createSelector, lruMemoize } from 'reselect'
export interface RootState {
todos: {
id: number
completed: boolean
title: string
description: string
}[]
alerts: { id: number; read: boolean }[]
}
const selectTodoIds = createSelector(
[(state: RootState) => state.todos],
todos => todos.map(todo => todo.id),
{
memoize: lruMemoize,
memoizeOptions: {
equalityCheck: shallowEqual,
resultEqualityCheck: shallowEqual,
maxSize: 10
},
argsMemoize: lruMemoize,
argsMemoizeOptions: {
equalityCheck: shallowEqual,
resultEqualityCheck: shallowEqual,
maxSize: 10
}
}
)import { shallowEqual } from 'react-redux'
import { createSelector, lruMemoize } from 'reselect'
const selectTodoIds = createSelector(
[state => state.todos],
todos => todos.map(todo => todo.id),
{
memoize: lruMemoize,
memoizeOptions: {
equalityCheck: shallowEqual,
resultEqualityCheck: shallowEqual,
maxSize: 10
},
argsMemoize: lruMemoize,
argsMemoizeOptions: {
equalityCheck: shallowEqual,
resultEqualityCheck: shallowEqual,
maxSize: 10
}
}
){/* END: lruMemoize/usingWithCreateSelector.ts */}
Using `lruMemoize` with `createSelectorCreator`
{/* START: lruMemoize/usingWithCreateSelectorCreator.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { shallowEqual } from 'react-redux'
import { createSelectorCreator, lruMemoize } from 'reselect'
export interface RootState {
todos: {
id: number
completed: boolean
title: string
description: string
}[]
alerts: { id: number; read: boolean }[]
}
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)
)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 => state.todos],
todos => todos.map(todo => todo.id)
){/* END: lruMemoize/usingWithCreateSelectorCreator.ts */}
Api WeakMapMemoize (`docs/api/weakMapMemoize.mdx`)
id: weakMapMemoize title: weakMapMemoize sidebar_label: weakMapMemoize hide_title: true
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
`weakMapMemoize`
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.
:::info Default since v5.0.0
Since v5.0.0, weakMapMemoize is the default memoization function used by createSelector for both memoize and argsMemoize. In v4.x the default was lruMemoize (previously named defaultMemoize) with a cache size of 1.
This means you generally do not need to pass memoize: weakMapMemoize or argsMemoize: weakMapMemoize yourself. The examples on this page pass them explicitly only to illustrate the behavior β a plain createSelector(...) call already uses weakMapMemoize.
:::
API Reference
Parameters
| Name | Description |
|---|---|
func |
The function to be memoized. |
options.resultEqualityCheck |
Optional. Compares a newly computed result against the previous one. If they compare equal, the previous result is returned instead, preserving reference equality for downstream consumers. |
options.maxSize |
Optional. Bounds how many results keyed by primitive arguments are retained. Available since 5.3.0. See Bounding cache size with maxSize below for the semantics. |
Returns
A memoized function with a .clearCache() method attached.
Type Parameters
| Name | Description |
|---|---|
Func |
The type of the function that is memoized. |
Usage Guide
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 andWeakMaps.
- It has an effectively infinite cache size, but you have no control over
Cons:
- There's currently no way to alter the argument comparisons. They're based on strict reference equality.
- Garbage collection only frees results keyed by object or function arguments. Results keyed by primitive arguments (strings, numbers, booleans) are held strongly, and are only released by calling
.clearCache(). A selector that keeps seeing new primitive values in the same argument position β ids, offsets, timestamps β grows its cache without bound. In development mode, thecacheSizeCheckwarns when a single argument position passes 1,000 distinct primitive values. As of 5.3.0, themaxSizeoption bounds this growth.
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:
{/* START: weakMapMemoize/cacheSizeProblem.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector } from 'reselect'
export interface RootState {
items: { id: number; category: string; name: string }[]
}
const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}
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!import { createSelector } from 'reselect'
const state = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}
const selectItemsByCategory = createSelector(
[state => state.items, (state, category) => 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!{/* END: weakMapMemoize/cacheSizeProblem.ts */}
Before you could solve this in a number of different ways:
- Set the
maxSizewithlruMemoize:
{/* START: weakMapMemoize/setMaxSize.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector, lruMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'
const selectItemsByCategory = createSelector(
[
(state: RootState) => state.items,
(state: RootState, category: string) => category
],
(items, category) => items.filter(item => item.category === category),
{
memoize: lruMemoize,
memoizeOptions: {
maxSize: 10
}
}
)import { createSelector, lruMemoize } from 'reselect'
const selectItemsByCategory = createSelector(
[state => state.items, (state, category) => category],
(items, category) => items.filter(item => item.category === category),
{
memoize: lruMemoize,
memoizeOptions: {
maxSize: 10
}
}
){/* END: weakMapMemoize/setMaxSize.ts */}
But this required having to know the cache size ahead of time.
- Create unique selector instances using
useMemo.
{/* START: weakMapMemoize/withUseMemo.tsx */}
<Tabs
groupId='language'
defaultValue='tsx'
values={[
{label: 'TypeScript', value: 'tsx'},
{label: 'JavaScript', value: 'jsx'},
]}>
import type { FC } from 'react'
import { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
import type { RootState } from './cacheSizeProblem'
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>
)
}import { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
const makeSelectItemsByCategory = category =>
createSelector([state => state.items], items =>
items.filter(item => item.category === category)
)
const MyComponent = ({ category }) => {
const selectItemsByCategory = useMemo(
() => makeSelectItemsByCategory(category),
[category]
)
const itemsByCategory = useSelector(selectItemsByCategory)
return (
<div>
{itemsByCategory.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
)
}{/* END: weakMapMemoize/withUseMemo.tsx */}
- 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.
{/* START: weakMapMemoize/cacheSizeSolution.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector, weakMapMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'
const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}
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!import { createSelector, weakMapMemoize } from 'reselect'
const state = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}
const selectItemsByCategory = createSelector(
[state => state.items, (state, category) => 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!{/* END: weakMapMemoize/cacheSizeSolution.ts */}
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.
Bounding cache size with `maxSize`
The infinite cache size eliminates earlier common pain points around reusing selector instances with varying arguments, which often required cumbersome workarounds like creating a unique selector instance per component instance. Each separate argument combination gets cached, and the code is simpler.
The tradeoff is that an "infinite cache size" can easily become a memory leak depending on arguments and usage patterns. Results keyed by object arguments are freed by garbage collection, but results keyed by primitive arguments are held strongly until you call .clearCache() (see Design Tradeoffs above). A selector that keeps seeing new primitive values in the same argument position β ids, offsets, timestamps, page numbers β grows its cache without bound.
As of 5.3.0, weakMapMemoize accepts a maxSize option that bounds this growth:
import { weakMapMemoize } from 'reselect'
const getVisibleItems = weakMapMemoize(
(items: Item[], from: number, to: number) => items.slice(from, to),
{ maxSize: 100 }
)This is implemented using a "generational" approach, rather than the LRU approach used by lruMemoize:
- Only results keyed by primitive arguments count toward
maxSize. Results keyed by objects or functions are already released by garbage collection. - After
maxSizenew results have been cached, the whole cache is demoted to a "previous generation", and later results build up a fresh cache. When the fresh cache fills up in turn, the previous generation is dropped in one step. - A call that finds its result in the previous generation promotes that result into the current one, so entries that keep getting used keep surviving the flips.
- Total retention is therefore bounded at roughly
2 * maxSizeresults, and eviction happens in batches rather than one entry at a time. This is by design: it keeps the per-call bookkeeping near zero, and there is no cost at all whenmaxSizeis not passed.
If you need an exact bound with per-entry LRU eviction order, use lruMemoize instead β the tradeoff is that its maxSize is exact but its bookkeeping runs on every call.
:::warning Bounding a createSelector selector requires both options
createSelector memoizes at two levels: argsMemoize caches by
the selector's call arguments, and memoize caches by the input selector
results. If you only set memoizeOptions: {maxSize}, the args cache still grows
without bound β and because it sits in front, it returns cached results before
the bounded cache is ever consulted. Pass maxSize in both options:
const selectVisibleItems = createSelector(
[
(state: RootState) => state.items,
(state: RootState, from: number) => from,
(state: RootState, from: number, to: number) => to
],
(items, from, to) => items.slice(from, to),
{
memoizeOptions: { maxSize: 100 },
argsMemoizeOptions: { maxSize: 100 }
}
)Since weakMapMemoize is the default for both levels, no memoize or argsMemoize fields are needed β the options objects configure the defaults.
:::
In development mode, the cacheSizeCheck warns when a selector shows the unbounded-growth pattern, which is a good signal that a particular selector would benefit from maxSize.
Keeping extra arguments away from unary input selectors
When a createSelector selector is called, every input selector receives all of the call's arguments β including input selectors that only use the first one. If such an input selector is itself a memoized selector, its cache is still keyed by the full argument list, so every distinct value of the extra argument creates a separate cache entry for it.
The results stay correct, and result functions do not rerun. But each of those cache entries is checked independently: when the state updates, a selector called with N distinct ids re-runs its input selectors N times instead of once. In a list where every item calls useSelector(state => selectSomeData(state, id)) and the state updates frequently, that per-update cost grows with the size of the list. The extra cache entries also hold N copies of the same result. A telltale sign is dependencyRecomputations() on the inner selector increasing by the number of list items on every state update, rather than by 1.
If an expensive input selector only depends on the state, wrap it so the extra arguments never reach it:
const selectSomeData = createSelector(
[
// Not `selectHeavyComputation` directly: this keeps `id` out of its cache key
(state: RootState) => selectHeavyComputation(state),
(state: RootState, id: string) => id
],
(heavyResult, id) => heavyResult[id]
)Examples
Using `weakMapMemoize` with `createSelector`
{/* START: weakMapMemoize/usingWithCreateSelector.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector, weakMapMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'
const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}
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')import { createSelector, weakMapMemoize } from 'reselect'
const state = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}
const selectItemsByCategory = createSelector(
[state => state.items, (state, category) => 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'){/* END: weakMapMemoize/usingWithCreateSelector.ts */}
Using `weakMapMemoize` with `createSelectorCreator`
{/* START: weakMapMemoize/usingWithCreateSelectorCreator.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelectorCreator, weakMapMemoize } from 'reselect'
import type { RootState } from './cacheSizeProblem'
const state: RootState = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}
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')import { createSelectorCreator, weakMapMemoize } from 'reselect'
const state = {
items: [
{ id: 1, category: 'Electronics', name: 'Wireless Headphones' },
{ id: 2, category: 'Books', name: 'The Great Gatsby' },
{ id: 3, category: 'Home Appliances', name: 'Blender' },
{ id: 4, category: 'Stationery', name: 'Sticky Notes' }
]
}
const createSelectorWeakMap = createSelectorCreator({
memoize: weakMapMemoize,
argsMemoize: weakMapMemoize
})
const selectItemsByCategory = createSelectorWeakMap(
[state => state.items, (state, category) => 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'){/* END: weakMapMemoize/usingWithCreateSelectorCreator.ts */}
Examples Tsconfig.Json (`docs/examples/tsconfig.json`)
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"allowUnusedLabels": true,
"isolatedModules": true,
"removeComments": false,
"checkJs": true,
"outDir": "dist",
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"skipLibCheck": true,
"allowJs": true,
"jsx": "preserve",
"noErrorTruncation": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"rootDir": "../..",
"paths": {
"reselect": ["../../src/index"], // @remap-prod-remove-line
"@internal/": ["../../src/"],
"createStructuredSelector/": ["./createStructuredSelector/"]
}
},
"include": ["/*.ts", "/*.tsx"]
}
Introduction How Does Reselect Work (`docs/introduction/how-does-reselect-work.mdx`)
id: how-does-reselect-work title: How Does Reselect Work? sidebar_label: How Does Reselect Work? hide_title: true
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:
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).
:::noteIf 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 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.
:::
Why Reselect Is Often Used With Redux
While Reselect can be used independently from Redux, it is a standard tool used in most Redux applications to help optimize calculations and UI updates:
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 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.
Introduction V5 Summary (`docs/introduction/v5-summary.mdx`)
id: v5-summary title: What's New in 5.0.0? sidebar_label: What's New in 5.0.0? hide_title: true
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 customizedmemoizeandargsMemoizefunctions, alongside their respective options (memoizeOptionsandargsMemoizeOptions). - The
createSelectorfunction now supports direct customization ofmemoizeandargsMemoizewithin its options object.
Memoization Functions
- Introduced new experimental memoization functions:
weakMapMemoizeandunstable_autotrackMemoize. (unstable_autotrackMemoizewas later removed in 5.3.0.) - Incorporated
memoizeandargsMemoizeinto 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 infiniteerror.
Selector API Enhancements
- Removed the second overload of
createStructuredSelectordue to its susceptibility to runtime errors.
Additional Functionalities
- Added
dependencyRecomputationsandresetDependencyRecomputationsto the output selector fields. These additions provide greater control and insight over input selectors, complementing the newargsMemoizeAPI. - 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 is an identity function .
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
- Switched the default memoization function used by
createSelectortoweakMapMemoize. - Renamed
defaultMemoizetolruMemoizeas it is no longer the default memoization function passed tocreateSelector. - Renamed
defaultEqualityChecktoreferenceEqualityCheck. - Renamed
DefaultMemoizeOptionstoLruMemoizeOptions. - Removed
ParametricSelectorandOutputParametricSelectortypes. Their functionalities are now integrated intoSelectorandOutputSelectorrespectively, which inherently support additional parameters.
Usage Best Practices (`docs/usage/best-practices.mdx`)
id: best-practices title: Best Practices and Common Mistakes sidebar_label: Best Practices and Common Mistakes hide_title: true
import Link from '@docusaurus/Link'
Best Practices and Common Mistakes
This page covers how to get the most out of Reselect's memoization, and the mistakes that defeat it. For the broader question of how selectors fit into a Redux application, see the Redux docs page on Deriving Data with Selectors.
Best Practices
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.todosor 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
)Common Mistakes
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
)Using the whole state as an input selector means the input changes on every dispatch, so the result function re-runs every time.
Reselect's development-only checks catch both of these mistakes in development: the inputStabilityCheck warns when an input selector returns a new reference for the same arguments, and the identityFunctionCheck warns when a result function returns its input unchanged.
Usage Handling Empty Array Results (`docs/usage/handling-empty-array-results.mdx`)
id: handling-empty-array-results title: Handling Empty Array Results sidebar_label: Handling Empty Array Results hide_title: true
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
Handling Empty Array Results
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:
{/* START: handling-empty-array-results/firstPattern.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector } from 'reselect'
export 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
}
)import { createSelector } from 'reselect'
const EMPTY_ARRAY = []
const selectCompletedTodos = createSelector([state => state.todos], todos => {
const completedTodos = todos.filter(todo => todo.completed === true)
return completedTodos.length === 0 ? EMPTY_ARRAY : completedTodos
}){/* END: handling-empty-array-results/firstPattern.ts */}
Or to avoid repetition, you can create a wrapper function and reuse it:
{/* START: handling-empty-array-results/fallbackToEmptyArray.ts */}
<Tabs
groupId='language'
defaultValue='ts'
values={[
{label: 'TypeScript', value: 'ts'},
{label: 'JavaScript', value: 'js'},
]}>
import { createSelector } from 'reselect'
import type { RootState } from './firstPattern'
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))
}
)import { createSelector } from 'reselect'
const EMPTY_ARRAY = []
export const fallbackToEmptyArray = array => {
return array.length === 0 ? EMPTY_ARRAY : array
}
const selectCompletedTodos = createSelector([state => state.todos], todos => {
return fallbackToEmptyArray(todos.filter(todo => todo.completed === true))
}){/* END: handling-empty-array-results/fallbackToEmptyArray.ts */}
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--- METRICS ---
- Files Extracted: 18
- Estimated Token Budget: ~34752 tokens
- Canonical Reference: https://codewiki.google/github.com/reduxjs/reselect