diff --git a/README.md b/README.md
index 0074f3d77..34bd845b3 100644
--- a/README.md
+++ b/README.md
@@ -1,707 +1,1782 @@
# Reselect
+[![npm package][npm-badge]][npm][![Coveralls][coveralls-badge]][coveralls][![GitHub Workflow Status][build-badge]][build]![TypeScript][typescript-badge]
+
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 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](https://redux.js.org/usage/deriving-data-selectors)** covers the purpose and motivation for selectors, why memoized selectors are useful, typical Reselect usage patterns, and using selectors with React-Redux.
+The **Redux docs usage page on [Deriving Data with Selectors](https://redux.js.org/usage/deriving-data-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](https://redux-toolkit.js.org) - no further installation needed.
+
+```ts
+import { createSelector } from '@reduxjs/toolkit'
+```
+
+### Standalone
+
+For standalone usage, install the `reselect` package:
+
+```bash
+# NPM
+npm install reselect
+
+# Yarn
+yarn add reselect
+
+# Bun
+bun add reselect
+
+# PNPM
+pnpm add reselect
+```
+
+---
+
+## Basic Usage
+
+Reselect exports a [`createSelector`] API, which generates memoized selector functions. [`createSelector`] accepts one or more [input selectors], which extract values from arguments, and a [result function] function that receives the extracted values and should return a derived value. If the generated [output selector] is called multiple times, the output will only be recalculated when the extracted values have changed.
+
+You can play around with the following **example** in [this CodeSandbox](https://codesandbox.io/s/reselect-example-g3k9gf?file=/src/index.js):
+
+```ts
+import { createSelector } from 'reselect'
+
+interface RootState {
+ todos: { id: number; completed: boolean }[]
+ alerts: { id: number; read: boolean }[]
+}
+
+const state: RootState = {
+ todos: [
+ { id: 0, completed: false },
+ { id: 1, completed: true }
+ ],
+ alerts: [
+ { id: 0, read: false },
+ { id: 1, read: true }
+ ]
+}
+
+const selectCompletedTodos = (state: RootState) => {
+ console.log('selector ran')
+ return state.todos.filter(todo => todo.completed === true)
+}
+
+selectCompletedTodos(state) // selector ran
+selectCompletedTodos(state) // selector ran
+selectCompletedTodos(state) // selector ran
+
+const memoizedSelectCompletedTodos = createSelector(
+ [(state: RootState) => state.todos],
+ todos => {
+ console.log('memoized selector ran')
+ return todos.filter(todo => todo.completed === true)
+ }
+)
+
+memoizedSelectCompletedTodos(state) // memoized selector ran
+memoizedSelectCompletedTodos(state)
+memoizedSelectCompletedTodos(state)
+
+console.log(selectCompletedTodos(state) === selectCompletedTodos(state)) //=> false
+
+console.log(
+ memoizedSelectCompletedTodos(state) === memoizedSelectCompletedTodos(state)
+) //=> true
+```
+
+As you can see from the example above, `memoizedSelectCompletedTodos` does not run the second or third time, but we still get the same return value as last time.
+
+In addition to skipping unnecessary recalculations, `memoizedSelectCompletedTodos` returns the existing result reference if there is no recalculation. This is important for libraries like [React-Redux] or [React] that often rely on reference equality checks to optimize UI updates.
+
+---
+
+## Table of Contents
+
+- [Installation](#installation)
+ - [Redux Toolkit](#redux-toolkit)
+ - [Standalone](#standalone)
+- [Basic Usage](#basic-usage)
+- [Terminology](#terminology)
+- [How Does Reselect Work?](#how-does-reselect-work)
+ - [Cascading Memoization](#cascading-memoization)
+ - [Why Reselect Is Often Used With Redux](#why-reselect-is-often-used-with-redux)
+- [API](#api)
+ - [**`createSelector`**][`createSelector`]
+ - [**`createSelectorCreator`**][`createSelectorCreator`]
+ - [**`createStructuredSelector`**][`createStructuredSelector`]
+ - [**`defaultMemoize`**][`defaultMemoize`]
+ - [**`weakMapMemoize`**][`weakMapMemoize`]
+ - [**`unstable_autotrackMemoize`**][`unstable_autotrackMemoize`]
+- [Debugging Tools](#debuggingtools)
+ - [Development-Only Stability Checks](#development-only-stability-checks)
+ - [Output Selector Fields](#output-selector-fields)
+- [What's New in 5.0.0?](#v5summary)
+- [Optimizing Reselect](#optimizing-reselect)
+- [FAQ](#faq)
+ - [Why isn’t my selector recomputing when the input state changes?](#why-isnt-my-selector-recomputing-when-the-input-state-changes)
+ - [Why is my selector recomputing when the input state stays the same?](#why-is-my-selector-recomputing-when-the-input-state-stays-the-same)
+ - [Can I use Reselect without Redux?](#can-i-use-reselect-without-redux)
+ - [How do I create a selector that takes an argument?](#how-do-i-create-a-selector-that-takes-an-argument)
+ - [Can the memoization behavior be customized?](#can-the-memoization-behavior-be-customized)
+ - [How do I test a selector?](#how-do-i-test-a-selector)
+ - [Can I share a selector across multiple component instances?](#can-i-share-a-selector-across-multiple-component-instances)
+ - [Are there TypeScript Typings?](#are-there-typescript-typings)
+ - [I am seeing a TypeScript error: `Type instantiation is excessively deep and possibly infinite`](#i-am-seeing-a-typescript-error-type-instantiation-is-excessively-deep-and-possibly-infinite)
+ - [How can I make a curried selector?](#how-can-i-make-a-curried-selector)
+ - [How can I make pre-typed version of `createSelector` for my root state?](#how-can-i-make-pre-typed-version-of-createselector-for-my-root-state)
+ - [What if I want to use `createSelector` without memoization?](#what-if-i-want-to-use-createselector-without-memoization)
+- [External References](#external-references)
+- [Related Projects](#related-projects)
+- [License](#license)
+- [Prior Art and Inspiration](#prior-art-and-inspiration)
+
+---
+
+## Terminology
+
+- [**Selector Function**](#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**](#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**](#output-selector): The actual memoized selectors created by [`createSelector`].
+- [**Result Function**](#result-function): The function that comes after the [input selectors]. It takes the [input selectors]' return values as arguments and returns a result.
+- [**`Dependencies`**](#dependencies): Same as [input selectors]. They are what the [output selector] "depends" on.
+
+The below example serves as a visual aid:
+
+```ts
+const outputSelector = createSelector(
+ [inputSelector1, inputSelector2, inputSelector3], // synonymous with `dependencies`.
+ resultFunc // Result function
+)
+```
+
+
+
+---
+
+## How Does Reselect Work?
+
+Reselect, at its core, is a library for creating memoized selectors in JavaScript applications. Its primary role is to efficiently compute derived data based on provided inputs. A key aspect of Reselect's internal mechanism is how it orchestrates the flow of arguments from the final selector to its constituent [input selectors].
+
+```ts
+const finalSelector = (...args) => {
+ const extractedValues = inputSelectors.map(inputSelector =>
+ inputSelector(...args)
+ )
+ return resultFunc(...extractedValues)
+}
+```
+
+In this pattern, the `finalSelector` is composed of several [input selectors], **all receiving the same arguments as the final selector**. Each input selector processes its part of the data, and the results are then combined and further processed by the [result function]. Understanding this argument flow is crucial for appreciating how Reselect optimizes data computation and minimizes unnecessary recalculations.
+
+
+
+### Cascading Memoization
+
+Reselect uses a two-stage "cascading" approach to memoizing functions:
+
+Detailed Explanation: Cascading Memoization
+
+The way Reselect works can be broken down into multiple parts:
+
+1. **Initial Run**: On the first call, Reselect runs all the [input selectors], gathers their results, and passes them to the [result function].
+
+2. **Subsequent Runs**: For subsequent calls, Reselect performs two levels of checks:
+
+ - **First Level**: It compares the current arguments with the previous ones (done by `argsMemoize`).
+
+ - If they're the same, it returns the cached result without running the [input selectors] or the [result function].
+
+ - If they differ, it proceeds ("cascades") to the second level.
+
+ - **Second Level**: It runs the [input selectors] and compares their current results with the previous ones (done by `memoize`).
+ > [!NOTE]
+ > If any one of the [input selectors] return a different result, all [input selectors] will recalculate.
+ - If the results are the same, it returns the cached result without running the [result function].
+ - If the results differ, it runs the [result function].
+
+This behavior is what we call **_Cascading Double-Layer Memoization_**.
+
+#### Reselect Vs Standard Memoization
+
+##### Standard Memoization
+
+
+
+_Standard memoization only compares arguments. If they're the same, it returns the cached result._
+
+##### Memoization with Reselect
+
+
+
+_Reselect adds a second layer of checks with the [input selectors]. This is crucial in [Redux] applications where state references change frequently._
+
+A normal [memoization] function will compare the arguments, and if they are the same as last time, it will skip running the function and return the cached result. However, Reselect enhances this by introducing a second tier of checks via its [input selectors]. It's possible that the arguments passed to these [input selectors] may change, yet their results remain the same. When this occurs, Reselect avoids re-executing the [result function], and returns the cached result.
+
+This feature becomes crucial in [Redux] applications, where the `state` changes its reference anytime an `action` is dispatched.
+
+> [!NOTE]
+> The [input selectors] take the same arguments as the [output selector].
+
+
+
+### 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:
+
+Detailed Explanation: Reselect and Redux Optimization
+
+Imagine you have a selector like this:
+
+```ts
+const selectCompletedTodos = (state: RootState) =>
+ state.todos.filter(todo => todo.completed === true)
+```
+
+So you decide to memoize it:
+
+```ts
+const selectCompletedTodos = someMemoizeFunction((state: RootState) =>
+ state.todos.filter(todo => todo.completed === true)
+)
+```
+
+Then you update `state.alerts`:
+
+```ts
+store.dispatch(toggleRead(0))
+```
+
+Now when you call `selectCompletedTodos`, it re-runs, because we have effectively broken memoization.
+
+```ts
+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:
+
+```ts
+const selectCompletedTodos = createSelector(
+ [(state: RootState) => state.todos],
+ todos => todos.filter(todo => todo.completed === true)
+)
+```
+
+And now we have achieved memoization:
+
+```ts
+selectCompletedTodos(store.getState())
+// Will not run, and the cached result will be returned.
+selectCompletedTodos(store.getState())
+store.dispatch(toggleRead(0))
+// The `input selectors` will run, but the `result function` is
+// skipped and the cached result will be returned.
+selectCompletedTodos(store.getState())
+```
+
+Even when the overall `state` changes, Reselect ensures efficient memoization through its unique approach. The [result function] doesn't re-run if the relevant part of the `state` (in this case `state.todos`), remains unchanged. This is due to Reselect's [**_Cascading Double-Layer Memoization_**][**_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.
+
+
+
+
+
+---
+
+## API
+
+
+
+### createSelector(...inputSelectors | [inputSelectors], resultFunc, createSelectorOptions?)
+
+Description
+
+Accepts one or more "[input selectors]" (either as separate arguments or a single array),
+a single "[result function]", and an optional options object, and
+generates a memoized selector function.
+
+Parameters
+
+| Name | Description |
+| :----------------------- | :-------------------------------------------------------------------------------- |
+| `inputSelectors` | An array of [input selectors], can also be passed as separate arguments. |
+| `resultFunc` | A function that takes the results of the [input selectors] as separate arguments. |
+| `createSelectorOptions?` | An optional options object that allows for further customization per selector. |
+
+Returns
+
+A memoized [output selector].
+
+Type parameters
+
+| Name | Description |
+| :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `InputSelectors` | The type of the [input selectors] array. |
+| `Result` | The return type of the [result function] as well as the [output selector]. |
+| `OverrideMemoizeFunction` | The type of the optional `memoize` function that could be passed into the options object to override the original `memoize` function that was initially passed into [`createSelectorCreator`]. |
+| `OverrideArgsMemoizeFunction` | The type of the optional `argsMemoize` function that could be passed into the options object to override the original `argsMemoize` function that was initially passed into [`createSelectorCreator`]. |
+
+
+
+
+
+---
+
+
+
+### createSelectorCreator(memoize | options, ...memoizeOptions)
+
+Description
+
+Accepts either a `memoize` function and `...memoizeOptions` rest parameter, or since 5.0.0 an `options` object containing a `memoize` function and creates a custom selector creator function.
+
+Parameters (since 5.0.0)
+
+| Name | Description |
+| :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `options` | An options object containing the `memoize` function responsible for memoizing the `resultFunc` inside [`createSelector`] (e.g., `defaultMemoize` 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., `defaultMemoize` or `weakMapMemoize`). **`Default`** `defaultMemoize` |
+| `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., `defaultMemoize` or `weakMapMemoize`). since 5.0.0 |
+| `options.memoizeOptions?` | Optional configuration options for the `memoize` function. These options are passed to the `memoize` function as the second argument. since 5.0.0 |
+
+Parameters
+
+| Name | Description |
+| :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `memoize` | The `memoize` function responsible for memoizing the `resultFunc` inside [`createSelector`] (e.g., `defaultMemoize` 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., `defaultMemoize` 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., `defaultMemoize` or `weakMapMemoize`). If none is explicitly provided, `defaultMemoize` will be used. |
+
+
+
+Examples
+
+##### Using `options` (since 5.0.0)
+
+```ts
+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 `defaultMemoize`.
+
+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:
+
+```ts
+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:
+
+```ts
+customMemoize(resultFunc, option1, option2, option3)
+```
+
+##### Additional Examples
+
+###### Customize `equalityCheck` for `defaultMemoize`
+
+```js
+import { createSelectorCreator, defaultMemoize } from 'reselect'
+import isEqual from 'lodash.isequal'
+
+// create a "selector creator" that uses lodash.isequal instead of ===
+const createDeepEqualSelector = createSelectorCreator(defaultMemoize, 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
+
+```js
+import { createSelectorCreator } from 'reselect'
+import memoize from 'lodash.memoize'
+
+const hashFn = (...args) =>
+ args.reduce((acc, val) => acc + '-' + JSON.stringify(val), '')
+
+const customSelectorCreator = createSelectorCreator(memoize, hashFn)
+
+const selector = customSelectorCreator(
+ [state => state.a, state => state.b],
+ (a, b) => a + b
+)
+```
+
+
+
+
+
+---
+
+
+
+### createStructuredSelector({ inputSelectorsObject }, selectorCreator = createSelector)
+
+Description
+
+A convenience function that simplifies returning an object made up of selector results.
+
+Parameters
+
+| Name | Description |
+| :--------------------- | :--------------------------------------------------------------------- |
+| `inputSelectorsObject` | A key value pair consisting of input selectors. |
+| `selectorCreator?` | A custom selector creator function. It defaults to [`createSelector`]. |
+
+Returns
+
+A memoized structured selector.
+
+Type parameters
+
+| Name | Description |
+| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `InputSelectorsObject` | The shape of the [input selectors] object. |
+| `MemoizeFunction` | The type of the memoize function that is used to create the structured selector. It defaults to `defaultMemoize`. |
+| `ArgsMemoizeFunction` | The type of the of the memoize function that is used to memoize the arguments passed into the generated structured selector. It defaults to `defaultMemoize`. |
+
+
+
+Examples
+
+##### Modern Use Case
+
+```ts
+import { createSelector, createStructuredSelector } from 'reselect'
+
+interface RootState {
+ todos: {
+ id: number
+ completed: boolean
+ title: string
+ description: string
+ }[]
+ alerts: { id: number; read: boolean }[]
+}
+
+// This:
+const structuredSelector = createStructuredSelector(
+ {
+ todos: (state: RootState) => state.todos,
+ alerts: (state: RootState) => state.alerts,
+ todoById: (state: RootState, id: number) => state.todos[id]
+ },
+ createSelector
+)
+
+// Is essentially the same as this:
+const selector = createSelector(
+ [
+ (state: RootState) => state.todos,
+ (state: RootState) => state.alerts,
+ (state: RootState, id: number) => state.todos[id]
+ ],
+ (todos, alerts, todoById) => {
+ return {
+ todos,
+ alerts,
+ todoById
+ }
+ }
+)
+```
+
+In your component:
+
+```tsx
+interface Props {
+ id: number
+}
+
+const MyComponent: FC = ({ id }) => {
+ const { todos, alerts, todoById } = useSelector(state =>
+ structuredSelector(state, id)
+ )
+
+ return (
+
+ Next to do is:
+
{todoById.title}
+
Description: {todoById.description}
+
+
All other to dos:
+ {todos.map(todo => (
+
{todo.title}
+ ))}
+
+
+ )
+}
+```
+
+##### Simple Use Case
+
+```ts
+const selectA = state => state.a
+const selectB = state => state.b
+
+// The result function in the following selector
+// is simply building an object from the input selectors
+const structuredSelector = createSelector(selectA, selectB, (a, b) => ({
+ a,
+ b
+}))
+
+const result = structuredSelector({ a: 1, b: 2 }) // will produce { x: 1, y: 2 }
+```
+
+
+
+
+
+---
+
+### Memoization Functions
+
+Reselect comes with a selection of memoization functions, each uniquely designed to address different scenarios and performance requirements. By effectively leveraging these functions, you can significantly enhance the efficiency and responsiveness of your applications.
+
+
+
+#### defaultMemoize(func, equalityCheckOrOptions = defaultEqualityCheck)
+
+Description
+
+The standard memoize function used by [`createSelector`].
+
+It has a default cache size of 1. This means it always recalculates when the value of an argument changes. However, this can be customized as needed with a specific max cache size (since 4.1.0).
+
+It determines if an argument has changed by calling the `equalityCheck` function. As `defaultMemoize` is designed to be used with immutable data, the default `equalityCheck` function checks for changes using [reference equality][Reference Equality Check]:
+
+```ts
+const defaultEqualityCheck = (previousValue: any, currentValue: any) => {
+ return previousValue === currentValue
+}
+```
+
+Parameters
+
+| Name | Description |
+| :----------------------- | :---------------------------------------------------------- |
+| `func` | The function to be memoized. |
+| `equalityCheckOrOptions` | Either an `equality check` function or an `options` object. |
+
+Since 4.1.0, `defaultMemoize` also accepts an options object as its first argument instead of an `equalityCheck` function. The `options` object may contain:
+
+```ts
+type EqualityFn = (a: any, b: any) => boolean
+
+interface DefaultMemoizeOptions {
+ equalityCheck?: EqualityFn
+ resultEqualityCheck?: EqualityFn
+ 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 |
+
+> [!WARNING]
+> If `resultEqualityCheck` is used inside `argsMemoizeOptions` it has no effect.
+
+Returns
+
+A memoized function with a `.clearCache()` method attached.
+
+Type parameters
+
+| Name | Description |
+| :----- | :----------------------------------------- |
+| `Func` | The type of the function that is memoized. |
+
+
+
+Examples
+
+###### Using `defaultMemoize` with [`createSelector`]
+
+```ts
+import { shallowEqual } from 'react-redux'
+import { createSelector } from 'reselect'
+
+const selectTodoIds = createSelector(
+ [(state: RootState) => state.todos],
+ todos => todos.map(todo => todo.id),
+ {
+ memoizeOptions: {
+ equalityCheck: shallowEqual,
+ resultEqualityCheck: shallowEqual,
+ maxSize: 10
+ },
+ argsMemoizeOptions: {
+ equalityCheck: shallowEqual,
+ resultEqualityCheck: shallowEqual,
+ maxSize: 10
+ }
+ }
+)
+```
+
+###### Using `defaultMemoize` with [`createSelectorCreator`]
+
+```ts
+import { shallowEqual } from 'react-redux'
+import { createSelectorCreator, defaultMemoize } from 'reselect'
+
+const createSelectorShallowEqual = createSelectorCreator({
+ memoize: defaultMemoize,
+ memoizeOptions: {
+ equalityCheck: shallowEqual,
+ resultEqualityCheck: shallowEqual,
+ maxSize: 10
+ },
+ argsMemoize: defaultMemoize,
+ argsMemoizeOptions: {
+ equalityCheck: shallowEqual,
+ resultEqualityCheck: shallowEqual,
+ maxSize: 10
+ }
+})
+
+const selectTodoIds = createSelectorShallowEqual(
+ [(state: RootState) => state.todos],
+ todos => todos.map(todo => todo.id)
+)
+```
+
+
+
+
+
+---
+
+
+
+#### weakMapMemoize(func) - (since 5.0.0)
+
+Description
+
+[`defaultMemoize`] has to be explicitly configured to have a cache size larger than 1, and uses an LRU cache internally.
+
+`weakMapMemoize` creates a tree of [`WeakMap`]-based cache nodes based on the identity of the arguments it's been called with (in this case, the extracted values from your input selectors). **This allows `weakMapMemoize` to have an effectively infinite cache size**. Cache results will be kept in memory as long as references to the arguments still exist, and then cleared out as the arguments are garbage-collected.
+
+Design Tradeoffs
+
+- Pros:
+
+ - It has an effectively infinite cache size, but you have no control over
+ how long values are kept in cache as it's based on garbage collection and [`WeakMap`]s.
+
+- Cons:
+ - There's currently no way to alter the argument comparisons. They're based on [strict reference equality][Reference Equality Check].
+
+
+
+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:
+
+```ts
+useSelector(state => selectSomeData(state, id))
+```
+
+Prior to `weakMapMemoize`, you had this problem:
+
+```ts
+interface RootState {
+ items: { id: number; category: string; name: string }[]
+}
+
+const selectItemsByCategory = createSelector(
+ [
+ (state: RootState) => state.items,
+ (state: RootState, category: string) => category
+ ],
+ (items, category) => items.filter(item => item.category === category)
+)
+
+selectItemsByCategory(state, 'Electronics') // Selector runs
+selectItemsByCategory(state, 'Electronics')
+selectItemsByCategory(state, 'Stationery') // Selector runs
+selectItemsByCategory(state, 'Electronics') // Selector runs again!
+```
+
+Before you could solve this in a number of different ways:
+
+1. Set the `maxSize` with [`defaultMemoize`]:
+
+```ts
+const selectItemsByCategory = createSelector(
+ [
+ (state: RootState) => state.items,
+ (state: RootState, category: string) => category
+ ],
+ (items, category) => items.filter(item => item.category === category),
+ {
+ memoizeOptions: {
+ maxSize: 10
+ }
+ }
+)
+```
+
+But this required having to know the cache size ahead of time.
+
+2. Create unique selector instances using [`useMemo`].
+
+```tsx
+const makeSelectItemsByCategory = (category: string) =>
+ createSelector([(state: RootState) => state.items], items =>
+ items.filter(item => item.category === category)
+ )
+
+interface Props {
+ category: string
+}
+
+const MyComponent: FC = ({ category }) => {
+ const selectItemsByCategory = useMemo(
+ () => makeSelectItemsByCategory(category),
+ [category]
+ )
+
+ const itemsByCategory = useSelector(selectItemsByCategory)
+
+ return (
+
+
+---
+
+
+
+#### unstable_autotrackMemoize(func) - (since 5.0.0)
+
+Description
+
+Uses an "auto-tracking" approach inspired by the work of the Ember Glimmer team. It uses a Proxy to wrap arguments and track accesses to nested fields in your selector on first read. Later, when the selector is called with new arguments, it identifies which accessed fields have changed and only recalculates the result if one or more of those accessed fields have changed. This allows it to be more precise than the shallow equality checks in `defaultMemoize`.
+
+> [!WARNING]
+> This API is still experimental and undergoing testing.
+
+Design Tradeoffs
+
+- Pros:
+
+ - It is likely to avoid excess calculations and recalculate fewer times than `defaultMemoize` will, which may also result in fewer component re-renders.
+
+- Cons:
+
+ - It only has a cache size of 1.
+ - It is slower than `defaultMemoize`, because it has to do more work. (How much slower is dependent on the number of accessed fields in a selector, number of calls, frequency of input changes, etc)
+ - It can have some unexpected behavior. Because it tracks nested field accesses, cases where you don't access a field will not recalculate properly. For example, a badly-written selector like:
+
+ ```ts
+ createSelector([state => state.todos], todos => todos)
+ ```
+
+ that just immediately returns the extracted value will never update, because it doesn't see any field accesses to check.
+
+
+
+Use Cases
+
+- It is likely best used for cases where you need to access specific nested fields in data, and avoid recalculating if other fields in the same data objects are immutably updated.
+
+
+
+Parameters
+
+| Name | Description |
+| :----- | :--------------------------- |
+| `func` | The function to be memoized. |
+
+Returns
+
+A memoized function with a `.clearCache()` method attached.
+
+Type parameters
+
+| Name | Description |
+| :----- | :----------------------------------------- |
+| `Func` | The type of the function that is memoized. |
+
+
+
+Examples
+
+###### Using `unstable_autotrackMemoize` with [`createSelector`]
+
+```ts
+import { unstable_autotrackMemoize, createSelector } from 'reselect'
+
+const selectTodoIds = createSelector(
+ [(state: RootState) => state.todos],
+ todos => todos.map(todo => todo.id),
+ { memoize: unstable_autotrackMemoize }
+)
+```
+
+###### Using `unstable_autotrackMemoize` with [`createSelectorCreator`]
+
+```ts
+import { unstable_autotrackMemoize, createSelectorCreator } from 'reselect'
+
+const createSelectorAutotrack = createSelectorCreator({
+ memoize: unstable_autotrackMemoize
+})
+
+const selectTodoIds = createSelectorAutotrack(
+ [(state: RootState) => state.todos],
+ todos => todos.map(todo => todo.id)
+)
+```
+
+
+
+
+
+---
+
+
+
+## Debugging Tools
+
+
+
+### Development-Only Stability Checks
+
+Reselect includes extra checks in development mode to help catch and warn about mistakes in selector behavior.
+
+
+
+#### `inputStabilityCheck`
+
+Due to how [**_Cascading Memoization_**] works in Reselect, it is crucial that your [input selectors] do not return a new reference on each run. If an [input selector][input selectors] always returns a new reference, like
+
+```ts
+state => ({ a: state.a, b: state.b })
+```
+
+or
+
+```ts
+state => state.todos.map(todo => todo.id)
+```
+
+that will cause the selector to never memoize properly.
+Since this is a common mistake, we've added a development mode check to catch this. By default, [`createSelector`] will now run the [input selectors] twice during the first call to the selector. If the result appears to be different for the same call, it will log a warning with the arguments and the two different sets of extracted input values.
+
+```ts
+type StabilityCheckFrequency = 'always' | 'once' | 'never'
+```
+
+| Possible Values | Description |
+| :-------------- | :---------------------------------------------- |
+| `once` | Run only the first time the selector is called. |
+| `always` | Run every time the selector is called. |
+| `never` | Never run the input stability check. |
+
+> [!IMPORTANT]
+> The input stability check is automatically disabled in production environments.
+
+You can configure this behavior in two ways:
+
+
+
+##### 1. Globally through `setInputStabilityCheckEnabled`:
+
+A `setInputStabilityCheckEnabled` function is exported from Reselect, which should be called with the desired setting.
+
+```ts
+import { setInputStabilityCheckEnabled } from 'reselect'
+
+// Run only the first time the selector is called. (default)
+setInputStabilityCheckEnabled('once')
+
+// Run every time the selector is called.
+setInputStabilityCheckEnabled('always')
+
+// Never run the input stability check.
+setInputStabilityCheckEnabled('never')
+```
+
+##### 2. Per selector by passing an `inputStabilityCheck` option directly to [`createSelector`]:
+
+```ts
+// Create a selector that double-checks the results of [`input selectors`][Input Selectors] every time it runs.
+const selectCompletedTodosLength = createSelector(
+ [
+ // 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.
+ { inputStabilityCheck: 'always' }
+)
+```
+
+> [!WARNING]
+> This will override the global input stability check set by calling `setInputStabilityCheckEnabled`.
-[![GitHub Workflow Status][build-badge]][build]
-[![npm package][npm-badge]][npm]
-[![Coveralls][coveralls-badge]][coveralls]
+
-## Installation
+### Output Selector Fields
-### Redux Toolkit
+The output selectors created by createSelector have several additional properties attached to them:
-While Reselect is not exclusive to Redux, it is already included by default in [the official Redux Toolkit package](https://redux-toolkit.js.org) - no further installation needed.
+| 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]. |
-```js
-import { createSelector } from '@reduxjs/toolkit'
-```
+
-### Standalone
+---
-For standalone usage, install the `reselect` package:
+
-```bash
-npm install reselect
+## What's New in 5.0.0?
-yarn add reselect
-```
+Version 5.0.0 introduces several new features and improvements:
-## Basic Usage
+- **Customization Enhancements**:
-Reselect exports a `createSelector` API, which generates memoized selector functions. `createSelector` accepts one or more "input" selectors, which extract values from arguments, and an "output" selector that receives the extracted values and should return a derived value. If the generated selector is called multiple times, the output will only be recalculated when the extracted values have changed.
+ - Added the ability to pass an options object to [`createSelectorCreator`], allowing for customized `memoize` and `argsMemoize` functions, alongside their respective options (`memoizeOptions` and `argsMemoizeOptions`).
+ - The [`createSelector`] function now supports direct customization of `memoize` and `argsMemoize` within its options object.
-You can play around with the following **example** in [this CodeSandbox](https://codesandbox.io/s/objective-waterfall-1z5y8?file=/src/index.js):
+- **Memoization Functions**:
-```js
-import { createSelector } from 'reselect'
+ - Introduced new experimental memoization functions: `weakMapMemoize` and `unstable_autotrackMemoize`.
+ - Incorporated `memoize` and `argsMemoize` into the [output selector fields] for debugging purposes.
-const selectShopItems = state => state.shop.items
-const selectTaxPercent = state => state.shop.taxPercent
+- **TypeScript Support and Performance**:
-const selectSubtotal = createSelector(selectShopItems, items =>
- items.reduce((subtotal, item) => subtotal + item.value, 0)
-)
+ - Discontinued support for TypeScript versions below 4.7, aligning with modern TypeScript features.
+ - Significantly improved TypeScript performance for nesting [output selector]s. The nesting limit has increased from approximately 8 to around 30 [output selector]s, greatly reducing the occurrence of the infamous `Type instantiation is excessively deep and possibly infinite` error.
-const selectTax = createSelector(
- selectSubtotal,
- selectTaxPercent,
- (subtotal, taxPercent) => subtotal * (taxPercent / 100)
-)
+- **Selector API Enhancements**:
-const selectTotal = createSelector(
- selectSubtotal,
- selectTax,
- (subtotal, tax) => ({ total: subtotal + tax })
-)
+ - Removed the second overload of `createStructuredSelector` due to its susceptibility to runtime errors.
+ - Added the `TypedStructuredSelectorCreator` utility type (_currently a work-in-progress_) to facilitate the creation of a pre-typed version of `createStructuredSelector` for your root state.
-const exampleState = {
- shop: {
- taxPercent: 8,
- items: [
- { name: 'apple', value: 1.2 },
- { name: 'orange', value: 0.95 }
- ]
- }
-}
+- **Additional Functionalities**:
-console.log(selectSubtotal(exampleState)) // 2.15
-console.log(selectTax(exampleState)) // 0.172
-console.log(selectTotal(exampleState)) // { total: 2.322 }
-```
+ - Added `dependencyRecomputations` and `resetDependencyRecomputations` to the [output selector fields]. These additions provide greater control and insight over [input selectors], complementing the new `argsMemoize` API.
+ - Introduced `inputStabilityCheck`, a development tool that runs the [input selectors] twice using the same arguments and triggers a warning If they return differing results for the same call.
-## Table of Contents
+These updates aim to enhance flexibility, performance, and developer experience. For detailed usage and examples, refer to the updated documentation sections for each feature.
-- [Installation](#installation)
- - [Redux Toolkit](#redux-toolkit)
- - [Standalone](#standalone)
-- [Basic Usage](#basic-usage)
-- [API](#api)
- - [createSelector(...inputSelectors | [inputSelectors], resultFunc, selectorOptions?)](#createselectorinputselectors--inputselectors-resultfunc-selectoroptions)
- - [defaultMemoize(func, equalityCheckOrOptions = defaultEqualityCheck)](#defaultmemoizefunc-equalitycheckoroptions--defaultequalitycheck)
- - [createSelectorCreator(memoize, ...memoizeOptions)](#createselectorcreatormemoize-memoizeoptions)
- - [Customize `equalityCheck` for `defaultMemoize`](#customize-equalitycheck-for-defaultmemoize)
- - [Use memoize function from Lodash for an unbounded cache](#use-memoize-function-from-lodash-for-an-unbounded-cache)
- - [createStructuredSelector({inputSelectors}, selectorCreator = createSelector)](#createstructuredselectorinputselectors-selectorcreator--createselector)
-- [Development-only checks](#development-only-checks)
- - [`inputStabilityCheck`](#inputstabilitycheck)
- - [Global configuration](#global-configuration)
- - [Per-selector configuration](#per-selector-configuration)
-- [FAQ](#faq)
- - [Q: Why isn’t my selector recomputing when the input state changes?](#q-why-isnt-my-selector-recomputing-when-the-input-state-changes)
- - [Q: Why is my selector recomputing when the input state stays the same?](#q-why-is-my-selector-recomputing-when-the-input-state-stays-the-same)
- - [Q: Can I use Reselect without Redux?](#q-can-i-use-reselect-without-redux)
- - [Q: How do I create a selector that takes an argument?](#q-how-do-i-create-a-selector-that-takes-an-argument)
- - [Q: The default memoization function is no good, can I use a different one?](#q-the-default-memoization-function-is-no-good-can-i-use-a-different-one)
- - [Q: How do I test a selector?](#q-how-do-i-test-a-selector)
- - [Q: Can I share a selector across multiple component instances?](#q-can-i-share-a-selector-across-multiple-component-instances)
- - [Q: Are there TypeScript Typings?](#q-are-there-typescript-typings)
- - [Q: How can I make a curried selector?](#q-how-can-i-make-a-curried-selector)
-- [Related Projects](#related-projects)
- - [re-reselect](#re-reselect)
- - [reselect-tools](#reselect-tools)
- - [reselect-debugger](#reselect-debugger)
-- [License](#license)
-- [Prior Art and Inspiration](#prior-art-and-inspiration)
+- **Breaking Changes**:
-## API
+ - Removed `ParametricSelector` and `OutputParametricSelector` types. Their functionalities are now integrated into `Selector` and `OutputSelector` respectively, which inherently support additional parameters.
-### createSelector(...inputSelectors | [inputSelectors], resultFunc, selectorOptions?)
+
-Accepts one or more "input selectors" (either as separate arguments or a single array), a single "output selector" / "result function", and an optional options object, and generates a memoized selector function.
+
-When the selector is called, each input selector will be called with all of the provided arguments. The extracted values are then passed as separate arguments to the output selector, which should calculate and return a final result. The inputs and result are cached for later use.
+---
-If the selector is called again with the same arguments, the previously cached result is returned instead of recalculating a new result.
+
-`createSelector` determines if the value returned by an input-selector has changed between calls using reference equality (`===`). Inputs to selectors created with `createSelector` should be immutable.
+## Optimizing Reselect
-By default, selectors created with `createSelector` have a cache size of 1. This means they always recalculate when the value of an input-selector changes, as a selector only stores the preceding value of each input-selector. This can be customized by passing a `selectorOptions` object with a `memoizeOptions` field containing options for the built-in `defaultMemoize` memoization function .
+### Common Mistakes
-```js
-const selectValue = createSelector(
- state => state.values.value1,
- state => state.values.value2,
- (value1, value2) => value1 + value2
-)
+Click to expand
-// You can also pass an array of selectors
-const selectTotal = createSelector(
- [state => state.values.value1, state => state.values.value2],
- (value1, value2) => value1 + value2
-)
+A somewhat common mistake is to write an [input selector][input selectors] that extracts a value or does some derivation, and a [result function] that just returns its result:
-// Selector behavior can be customized
-const customizedSelector = createSelector(
- state => state.a,
- state => state.b,
- (a, b) => a + b,
- {
- // New in 4.1: Pass options through to the built-in `defaultMemoize` function
- memoizeOptions: {
- equalityCheck: (a, b) => a === b,
- maxSize: 10,
- resultEqualityCheck: shallowEqual
- }
- }
+```ts
+// ❌ BROKEN: this will not memoize correctly, and does nothing useful!
+const brokenSelector = createSelector(
+ [(state: RootState) => state.todos],
+ todos => todos
)
```
-Selectors are typically called with a Redux `state` value as the first argument, and the input selectors extract pieces of the `state` object for use in calculations. However, it's also common to want to pass additional arguments, such as a value to filter by. Since input selectors are given all arguments, they can extract the additional arguments and pass them to the output selector:
+Any [result function] that just returns its inputs is incorrect! The [result function] should always have the transformation logic.
-```js
-const selectItemsByCategory = createSelector(
- [
- // Usual first input - extract value from `state`
- state => state.items,
- // Take the second arg, `category`, and forward to the output selector
- (state, category) => category
- ],
- // Output selector gets (`items, category)` as args
- (items, category) => items.filter(item => item.category === category)
+Similarly:
+
+```ts
+// ❌ BROKEN: this will not memoize correctly!
+const brokenSelector = createSelector(
+ [(state: RootState) => state],
+ state => state.todos
)
```
-### defaultMemoize(func, equalityCheckOrOptions = defaultEqualityCheck)
-
-`defaultMemoize` memoizes the function passed in the func parameter. It is the standard memoize function used by `createSelector`.
+
-`defaultMemoize` 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 (new in 4.1).
+### Handling Empty Array Results
-`defaultMemoize` determines if an argument has changed by calling the `equalityCheck` function. As `defaultMemoize` is designed to be used with immutable data, the default `equalityCheck` function checks for changes using reference equality:
+Click to expand
-```js
-function defaultEqualityCheck(previousVal, currentVal) {
- return currentVal === previousVal
-}
-```
+To reduce recalculations, use a predefined empty array when `array.filter` or similar methods result in an empty array.
-As of Reselect 4.1, `defaultMemoize` also accepts an options object as its first argument instead of `equalityCheck`. The options object may contain:
+So you can have a pattern like this:
```ts
-interface DefaultMemoizeOptions {
- equalityCheck?: EqualityFn
- resultEqualityCheck?: EqualityFn
- maxSize?: number
+interface RootState {
+ todos: {
+ id: number
+ title: string
+ description: string
+ completed: boolean
+ }[]
}
-```
-Available options are:
+const EMPTY_ARRAY: [] = []
-- `equalityCheck`: used to compare the individual arguments of the provided calculation function
-- `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 address the common `todos.map(todo => todo.id)` use case, where an update to another field in the original data causes a recalculate due to changed references, but the output is still effectively the same.
-- `maxSize`: the cache size for the selector. If `maxSize` is greater than 1, the selector will use an LRU cache internally
+const selectCompletedTodos = createSelector(
+ [(state: RootState) => state.todos],
+ todos => {
+ const completedTodos = todos.filter(todo => todo.completed === true)
+ return completedTodos.length === 0 ? EMPTY_ARRAY : completedTodos
+ }
+)
+```
-The returned memoized function will have a `.clearCache()` method attached.
+Or to avoid repetition, you can create a wrapper function and reuse it:
-`defaultMemoize` can also be used with `createSelectorCreator` to create a new selector factory that always has the same settings for each selector.
+```ts
+const EMPTY_ARRAY: [] = []
-### createSelectorCreator(memoize, ...memoizeOptions)
+export const fallbackToEmptyArray = (array: T[]) => {
+ return array.length === 0 ? EMPTY_ARRAY : array
+}
-`createSelectorCreator` can be used to make a customized version of `createSelector`.
+const selectCompletedTodos = createSelector(
+ [(state: RootState) => state.todos],
+ todos => {
+ return fallbackToEmptyArray(todos.filter(todo => todo.completed === true))
+ }
+)
+```
-The `memoize` argument is a memoization function to replace `defaultMemoize`.
+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:
-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:
+```ts
+const completedTodos = selectCompletedTodos(store.getState())
-```js
-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
-)
+store.dispatch(addTodo())
-const customSelector = customSelectorCreator(
- input1,
- input2,
- resultFunc // resultFunc will be passed as first argument to customMemoize
-)
+console.log(completedTodos === selectCompletedTodos(store.getState())) //=> true
```
-Internally `customSelector` calls the memoize function as follows:
+
-```js
-customMemoize(resultFunc, option1, option2, option3)
-```
+### Best Practices
-Here are some examples of how you might use `createSelectorCreator`:
+Click to expand
-#### Customize `equalityCheck` for `defaultMemoize`
+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:
-```js
-import { createSelectorCreator, defaultMemoize } from 'reselect'
-import isEqual from 'lodash.isequal'
+- Due to the [**_Cascading Memoization_**] in Reselect, The first layer of checks is upon the arguments that are passed to the [output selector], therefore it's best to maintain the same reference for the arguments as much as possible.
+- In [Redux], your state will change reference when updated. But it's best to keep the additional arguments as simple as possible, you can pass in objects or array as long as their reference does not change. Or you can pass in primitives like numbers for ids.
+- Keep your [input selectors] as simple as possible. It's best if they mostly consist of field accessors like `state => state.todos` or argument providers like `(state, id) => id`. You should not be doing any sort of calculation inside [input selectors], and you should definitely not be returning an object or array with a new reference each time.
+- The [result function] is only re-run as a last resort. So make sure to put any and all calculations inside your [result function]. That way, Reselect will only run those calculations if all other checks fail.
-// create a "selector creator" that uses lodash.isequal instead of ===
-const createDeepEqualSelector = createSelectorCreator(defaultMemoize, isEqual)
+This:
-// 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)
+```ts
+// ✔️ 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)
)
```
-#### Use memoize function from Lodash for an unbounded cache
-
-```js
-import { createSelectorCreator } from 'reselect'
-import memoize from 'lodash.memoize'
+Is preferable to this:
-let called = 0
-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) => {
- called++
- return a + b
- }
+```ts
+// ❌ This is not optimal!
+const selectorBad = createSelector(
+ [(state: RootState) => someExpensiveComputation(state.todos)],
+ someOtherCalculation
)
```
-### createStructuredSelector({inputSelectors}, selectorCreator = createSelector)
+
-`createStructuredSelector` is a convenience function for a common pattern that arises when using Reselect. The selector passed to a `connect` decorator often just takes the values of its input-selectors and maps them to keys in an object:
+
-```js
-const selectA = state => state.a
-const selectB = state => state.b
+---
-// The result function in the following selector
-// is simply building an object from the input selectors
-const structuredSelector = createSelector(selectA, selectB, (a, b) => ({
- a,
- b
-}))
-```
+## FAQ
-`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 `inputSelectors` argument, but with the selectors replaced with their values.
+### Why isn’t my selector recomputing when the input state changes?
-```js
-const selectA = state => state.a
-const selectB = state => state.b
+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](http://redux.js.org/docs/Troubleshooting.html).
-const structuredSelector = createStructuredSelector({
- x: selectA,
- y: selectB
-})
+### Why is my selector recomputing when the input state stays the same?
-const result = structuredSelector({ a: 1, b: 2 }) // will produce { x: 1, y: 2 }
-```
+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.
-Structured selectors can be nested:
+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.
-```js
-const nestedSelector = createStructuredSelector({
- subA: createStructuredSelector({
- selectorA,
- selectorB
- }),
- subB: createStructuredSelector({
- selectorC,
- selectorD
- })
-})
+Detailed Explanation: Selector Recomputations
+
+To delve deeper, you can determine which arguments are changing references too frequently by using the `argsMemoizeOptions` and `equalityCheck`. Consider the following example:
+
+```ts
+interface RootState {
+ todos: { id: number; completed: boolean }[]
+ alerts: { id: number; read: boolean; type: string }[]
+}
+
+const selectAlertsByType = createSelector(
+ [
+ (state: RootState) => state.alerts,
+ (state: RootState, type: string) => type
+ ],
+ (alerts, type) => alerts.filter(todo => todo.type === type),
+ {
+ argsMemoizeOptions: {
+ // This will check the arguments passed to the output selector.
+ equalityCheck: (a, b) => {
+ if (a !== b) {
+ console.log('Changed argument:', a, 'to', b)
+ }
+ return a === b
+ }
+ }
+ }
+)
```
-## Development-only checks
+
-### `inputStabilityCheck`
+### Can I use Reselect without [Redux]?
-In development, an extra check is conducted on your input selectors. It runs your input selectors an extra time with the same parameters, and warns in console if they return a different result (based on your `memoize` method).
+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.
-This is important, as an input selector returning a materially different result with the same parameters means that the output selector will be run unnecessarily, thus (potentially) creating a new result and causing rerenders.
+### How do I create a selector that takes an argument?
-```js
-const addNumbers = createSelector(
- // this input selector will always return a new reference when run
- // so cache will never be used
- (a, b) => ({ a, b }),
- ({ a, b }) => ({ total: a + b })
-)
-// instead, you should have an input selector for each stable piece of data
-const addNumbersStable = createSelector(
- (a, b) => a,
- (a, b) => b,
- (a, b) => ({
- total: a + b
- })
+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:
+
+```ts
+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)
)
```
-By default, this will only happen when the selector is first called. You can configure the check globally or per selector, to change it to always run when the selector is called, or to never run.
+Detailed Explanation: Selectors and Arguments
-_This check is disabled for production environments._
+When creating a selector that accepts arguments in Reselect, it's important to structure your input and [output selector]s appropriately. Here are key points to consider:
-#### Global configuration
+1. **Consistency in Arguments**: Ensure that all positional arguments across [input selectors] are of the same type for consistency.
-A `setInputStabilityCheckEnabled` function is exported from `reselect`, which should be called with the desired setting.
+2. **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].
-```js
-import { setInputStabilityCheckEnabled } from 'reselect'
+Suppose we have the following state structure:
-// run when selector is first called (default)
-setInputStabilityCheckEnabled('once')
+```ts
+interface RootState {
+ items: {
+ id: number
+ category: string
+ vendor: { id: number; name: string }
+ }[]
+ // ... other state properties ...
+}
+```
-// always run
-setInputStabilityCheckEnabled('always')
+To create a selector that filters `items` based on a `category` and excludes a specific `id`, you can set up your selectors as follows:
-// never run
-setInputStabilityCheckEnabled('never')
+```ts
+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)
+)
```
-#### Per-selector configuration
-
-A value can be passed as part of the selector options object, which will override the global setting for the given selector.
+Internally Reselect is doing this:
```ts
-const selectPersonName = createSelector(
- selectPerson,
- person => person.firstName + ' ' + person.lastName,
- // `inputStabilityCheck` accepts the same settings
- // as `setInputStabilityCheckEnabled`
- { inputStabilityCheck: 'never' }
-)
+// 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)
```
-## FAQ
+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:
-### Q: Why isn’t my selector recomputing when the input state changes?
+```ts
+const selectItems = (state: RootState) => state.items
-A: 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](http://redux.js.org/docs/Troubleshooting.html).
+// expects a number as the second argument
+const selectItemId = (state: RootState, itemId: number) => itemId
-The following example defines a simple selector that determines if the first todo item in an array of todos has been completed:
+// expects an object as the second argument
+const selectVendorName = (
+ state: RootState,
+ vendor: { id: number; name: string }
+) => vendor.name
-```js
-const selectIsFirstTodoComplete = createSelector(
- state => state.todos[0],
- todo => todo && todo.completed
+const selectItemById = createSelector(
+ [selectItems, selectItemId, selectVendorName],
+ (items, itemId, vendorName) => items[itemId]
)
```
-The following state update function **will not** work with `selectIsFirstTodoComplete`:
-
-```js
-export default function todos(state = initialState, action) {
- switch (action.type) {
- case COMPLETE_ALL:
- const areAllMarked = state.every(todo => todo.completed)
- // BAD: mutating an existing object
- return state.map(todo => {
- todo.completed = !areAllMarked
- return todo
- })
-
- default:
- return state
- }
-}
-```
+
-The following state update function **will** work with `selectIsFirstTodoComplete`:
+### Can the memoization behavior be customized?
-```js
-export default function todos(state = initialState, action) {
- switch (action.type) {
- case COMPLETE_ALL:
- const areAllMarked = state.every(todo => todo.completed)
- // GOOD: returning a new object each time with Object.assign
- return state.map(todo =>
- Object.assign({}, todo, {
- completed: !areAllMarked
- })
- )
-
- default:
- return state
- }
-}
-```
+Yes. The built-in `defaultMemoize` memoizer works great for a lot of use cases, but it can be customized or swapped out for a different memoizer. See [these examples](#customize-equalitycheck-for-defaultmemoize).
-If you are not using Redux and have a requirement to work with mutable data, you can use `createSelectorCreator` to replace the default memoization function and/or use a different equality check function. See [here](#use-memoize-function-from-lodash-for-an-unbounded-cache) and [here](#customize-equalitycheck-for-defaultmemoize) for examples.
+### How do I test a selector?
-### Q: Why is my selector recomputing when the input state stays the same?
+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.
-A: 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` that recomputes unexpectedly may be receiving a new object on each update whether the values it contains have changed or not. `createSelector` uses an identity check (`===`) to detect that an input has changed, so returning a new object on each update means that the selector will recompute on each update.
+Detailed Explanation: Testing Selectors
-```js
-import { REMOVE_OLD } from '../constants/ActionTypes'
+```ts
+interface RootState {
+ todos: { id: number; completed: boolean }[]
+ alerts: { id: number; read: boolean }[]
+}
-const initialState = [
- {
- text: 'Use Redux',
- completed: false,
- id: 0,
- timestamp: Date.now()
- }
-]
-
-export default function todos(state = initialState, action) {
- switch (action.type) {
- case REMOVE_OLD:
- return state.filter(todo => {
- return todo.timestamp + 30 * 24 * 60 * 60 * 1000 > Date.now()
- })
- default:
- return state
- }
+const state: RootState = {
+ todos: [
+ { id: 0, completed: false },
+ { id: 1, completed: true }
+ ],
+ alerts: [
+ { id: 0, read: false },
+ { id: 1, read: true }
+ ]
}
-```
-The following selector is going to recompute every time REMOVE_OLD is invoked because Array.filter always returns a new object. However, in the majority of cases the REMOVE_OLD action will not change the list of todos so the recomputation is unnecessary.
+// With `Vitest` or `Jest`
+test('selector unit test', () => {
+ const selectTodoIds = createSelector(
+ [(state: RootState) => state.todos],
+ todos => todos.map(({ id }) => id)
+ )
+ const firstResult = selectTodoIds(state)
+ const secondResult = selectTodoIds(state)
+ // Reference equality should pass.
+ expect(firstResult).toBe(secondResult)
+ // Deep equality should also pass.
+ expect(firstResult).toStrictEqual(secondResult)
+ selectTodoIds(state)
+ selectTodoIds(state)
+ selectTodoIds(state)
+ // The `Result Function` should not recalculate.
+ expect(selectTodoIds.recomputations()).toBe(1)
+ // `input selectors` should not recalculate.
+ expect(selectTodoIds.dependencyRecomputations()).toBe(1)
+})
-```js
-import { createSelector } from 'reselect'
+// With `Chai`
+test('selector unit test', () => {
+ const selectTodoIds = createSelector(
+ [(state: RootState) => state.todos],
+ todos => todos.map(({ id }) => id)
+ )
+ const firstResult = selectTodoIds(state)
+ const secondResult = selectTodoIds(state)
+ // Reference equality should pass.
+ expect(firstResult).to.equal(secondResult)
+ // Deep equality should also pass.
+ expect(firstResult).to.deep.equal(secondResult)
+ selectTodoIds(state)
+ selectTodoIds(state)
+ selectTodoIds(state)
+ // The `result function` should not recalculate.
+ expect(selectTodoIds.recomputations()).to.equal(1)
+ // `input selectors` should not recalculate.
+ expect(selectTodoIds.dependencyRecomputations()).to.equal(1)
+})
+```
-const todosSelector = state => state.todos
+
-export const selectVisibleTodos = createSelector(
- todosSelector,
- (todos) => {
- ...
- }
-)
-```
+### Can I share a selector across multiple component instances?
-You can eliminate unnecessary recomputations by returning a new object from the state update function only when a deep equality check has found that the list of todos has actually changed:
+Yes, although if they pass in different arguments, you will need to handle that in order for memoization to work consistently:
-```js
-import { REMOVE_OLD } from '../constants/ActionTypes'
-import isEqual from 'lodash.isequal'
+- Pass a larger `maxSize` if using `defaultMemoize` ( as of 4.1.0+)
+- Use [`weakMapMemoize`](#weakmapmemoize) (as of 5.0.0+)
-const initialState = [
- {
- text: 'Use Redux',
- completed: false,
- id: 0,
- timestamp: Date.now()
- }
-]
-
-export default function todos(state = initialState, action) {
- switch (action.type) {
- case REMOVE_OLD:
- const updatedState = state.filter(todo => {
- return todo.timestamp + 30 * 24 * 60 * 60 * 1000 > Date.now()
- })
- return isEqual(updatedState, state) ? state : updatedState
- default:
- return state
- }
-}
-```
+### Are there TypeScript Typings?
-Alternatively, the default `equalityCheck` function in the selector can be replaced by a deep equality check:
+Yes! Reselect is now written in TypeScript itself, so they should Just Work™.
-```js
-import { createSelectorCreator, defaultMemoize } from 'reselect'
-import isEqual from 'lodash.isequal'
+### I am seeing a TypeScript error: `Type instantiation is excessively deep and possibly infinite`
-const selectTodos = state => state.todos
+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](https://github.com/reduxjs/reselect/issues/534#issuecomment-956708953) for a discussion of the problem, as
+relating to nested selectors.
-// create a "selector creator" that uses lodash.isequal instead of ===
-const createDeepEqualSelector = createSelectorCreator(
- defaultMemoize,
- isEqual
-)
+### How can I make a [curried](https://github.com/hemanth/functional-programming-jargon#currying) selector?
-// use the new "selector creator" to create a selector
-const mySelector = createDeepEqualSelector(
- todosSelector,
- (todos) => {
- ...
- }
-)
+Selectors that take arguments are commonly used inside of React-Redux's `useSelector` by using a closure to pass along the extra arguments:
+
+```ts
+function TodosList({ category }) {
+ const filteredTodos = useSelector(state =>
+ selectTodosByCategory(state, category)
+ )
+}
```
-Always check that the cost of an alternative `equalityCheck` function or deep equality check in the state update function is not greater than the cost of recomputing every time. If recomputing every time does work out to be the cheaper option, it may be that for this case Reselect is not giving you any benefit over passing a plain `mapStateToProps` function to `connect`.
+If you prefer to use a curried form instead, you can create a curried selector with this recipe:
-### Q: Can I use Reselect without Redux?
+Detailed Explanation: Creating Curried Selectors
-A: 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.
+You can try this pattern:
+
+```ts
+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)
+}
-### Q: How do I create a selector that takes an argument?
+const selectTodoByIdCurried = currySelector(
+ createSelector(
+ [(state: RootState) => state.todos, (state: RootState, id: number) => id],
+ (todos, id) => todos.find(todo => todo.id === id)
+ )
+)
+```
-Conceptually, Reselect works like this internally:
+Or for reusability you can do this:
```ts
-const finalSelector = (...args) => {
- const extractedValues = inputFunctions.map(input => input(...args));
- return output(...extractedValues);
+import type { defaultMemoize, SelectorArray, UnknownMemoizer } from 'reselect'
+import { createSelector } from 'reselect'
+
+export const createCurriedSelector = <
+ InputSelectors extends SelectorArray,
+ Result,
+ OverrideMemoizeFunction extends UnknownMemoizer = typeof defaultMemoize,
+ OverrideArgsMemoizeFunction extends UnknownMemoizer = typeof defaultMemoize
+>(
+ ...args: Parameters<
+ typeof createSelector<
+ InputSelectors,
+ Result,
+ OverrideMemoizeFunction,
+ OverrideArgsMemoizeFunction
+ >
+ >
+) => {
+ return currySelector(createSelector(...args))
}
```
-In other words, all the arguments passed to the selector function are immediately passed to all of the inputs.
-
-As shown in the API reference section above, provide input selectors that extract the arguments and forward them to the output selector for calculation:
+This:
-```js
-const selectItemsByCategory = createSelector(
- [
- // Usual first input - extract value from `state`
- state => state.items,
- // Take the second arg, `category`, and forward to the output selector
- (state, category) => category
- ],
- // Output selector gets (`items, category)` as args
- (items, category) => items.filter(item => item.category === category)
+```ts
+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:
-More generally, you can have N arguments passed to the selector, and you can have M input functions extracting values from any of those arguments. All M extracted values get passed to the output function.
+```ts
+selectTodoByIdCurried(0)(state)
+```
-### Q: The default memoization function is no good, can I use a different one?
+As before you had to do this:
-A: We think it works great for a lot of use cases, but sure. See [these examples](#customize-equalitycheck-for-defaultmemoize).
+```ts
+const todoById = useSelector(state => selectTodoById(state, id))
+```
-### Q: How do I test a selector?
+Now you can do this:
-A: For a given input, a selector should always produce the same output. For this reason they are simple to unit test.
+```ts
+const todoById = useSelector(selectTodoByIdCurried(id))
+```
-```js
-const selector = createSelector(
- state => state.a,
- state => state.b,
- (a, b) => ({
- c: a * 2,
- d: b * 3
- })
-)
+Another thing you can do if you are using [React-Redux] is create a custom hook factory function:
-test('selector unit test', () => {
- assert.deepEqual(selector({ a: 1, b: 2 }), { c: 2, d: 6 })
- assert.deepEqual(selector({ a: 2, b: 3 }), { c: 4, d: 9 })
-})
+```ts
+import { useSelector } from 'react-redux'
+
+export const createParametricSelectorHook = <
+ Result,
+ Params extends readonly unknown[]
+>(
+ selector: (state: RootState, ...params: Params) => Result
+) => {
+ return (...args: Params) => {
+ return useSelector(state => selector(state, ...args))
+ }
+}
+
+const useSelectTodo = createParametricSelectorHook(selectTodoById)
```
-It may also be useful to check that the memoization function for a selector works correctly with the state update function (i.e. the reducer if you are using Redux). Each selector has a `recomputations` method that will return the number of times it has been recomputed:
+And then inside your component:
-```js
-suite('selector', () => {
- let state = { a: 1, b: 2 }
-
- const reducer = (state, action) => ({
- a: action(state.a),
- b: action(state.b)
- })
-
- const selector = createSelector(
- state => state.a,
- state => state.b,
- (a, b) => ({
- c: a * 2,
- d: b * 3
- })
- )
+```tsx
+import type { FC } from 'react'
- const plusOne = x => x + 1
- const id = x => x
-
- test('selector unit test', () => {
- state = reducer(state, plusOne)
- assert.deepEqual(selector(state), { c: 4, d: 9 })
- state = reducer(state, id)
- assert.deepEqual(selector(state), { c: 4, d: 9 })
- assert.equal(selector.recomputations(), 1)
- state = reducer(state, plusOne)
- assert.deepEqual(selector(state), { c: 6, d: 12 })
- assert.equal(selector.recomputations(), 2)
- })
-})
+interface Props {
+ id: number
+}
+
+const MyComponent: FC = ({ id }) => {
+ const todo = useSelectTodo(id)
+ return
{todo.title}
+}
```
-Additionally, selectors keep a reference to the last result function as `.resultFunc`. If you have selectors composed of many other selectors this can help you test each selector without coupling all of your tests to the shape of your state.
+
-For example if you have a set of selectors like this:
+### How can I make pre-typed version of [`createSelector`](#createselector) for my root state?
-**selectors.js**
+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.
-```js
-export const selectFirst = createSelector( ... )
-export const selectSecond = createSelector( ... )
-export const selectThird = createSelector( ... )
+Detailed Explanation: Pre-Typed `createSelector`
-export const myComposedSelector = createSelector(
- selectFirst,
- selectSecond,
- selectThird,
- (first, second, third) => first * second < third
-)
-```
+You can create a custom typed version of [`createSelector`] by defining a utility type that extends the original [`createSelector`] function. Here's an example:
-And then a set of unit tests like this:
+```ts
+import type {
+ OutputSelector,
+ Selector,
+ SelectorArray,
+ UnknownMemoizer
+} from 'reselect'
+import { createSelector } from 'reselect'
-**test/selectors.js**
+interface RootState {
+ todos: { id: number; completed: boolean }[]
+ alerts: { id: number; read: boolean }[]
+}
-```js
-// tests for the first three selectors...
-test("selectFirst unit test", () => { ... })
-test("selectSecond unit test", () => { ... })
-test("selectThird unit test", () => { ... })
-
-// We have already tested the previous
-// three selector outputs so we can just call `.resultFunc`
-// with the values we want to test directly:
-test("myComposedSelector unit test", () => {
- // here instead of calling selector()
- // we just call selector.resultFunc()
- assert(myComposedSelector.resultFunc(1, 2, 3), true)
- assert(myComposedSelector.resultFunc(2, 2, 1), false)
-})
+export type TypedCreateSelector<
+ State,
+ MemoizeFunction extends UnknownMemoizer = typeof defaultMemoize,
+ ArgsMemoizeFunction extends UnknownMemoizer = typeof defaultMemoize
+> = <
+ InputSelectors extends readonly Selector[],
+ 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 = createSelector
```
-Finally, each selector has a `resetRecomputations` method that sets
-recomputations back to 0. The intended use is for a complex selector that may
-have many independent tests and you don't want to manually manage the
-computation count or create a "dummy" selector for each test.
+> [!WARNING]: This approach currently only supports [input selectors] provided as a single array.
-### Q: Can I share a selector across multiple component instances?
+
-A: Yes, although it requires some planning.
+### What if I want to use [`createSelector`](#createselector) without memoization?
-As of Reselect 4.1, you can create a selector with a cache size greater than one by passing in a `maxSize` option under `memoizeOptions` for use with the built-in `defaultMemoize`.
+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`][Identity Function] and use it as the memoizers:
-Otherwise, selectors created using `createSelector` only have a cache size of one. This can make them unsuitable for sharing across multiple instances if the arguments to the selector are different for each instance of the component. There are a couple of ways to get around this:
+```ts
+const identity = any>(func: Func) => func
-- Create a factory function which returns a new selector for each instance of the component. This can be called in a React component inside the `useMemo` hook to generate a unique selector instance per component.
-- Create a custom selector with a cache size greater than one using `createSelectorCreator`
+const createNonMemoizedSelector = createSelectorCreator({
+ memoize: identity,
+ argsMemoize: identity
+})
+```
-### Q: Are there TypeScript Typings?
+
-A: Yes! Reselect is now written in TS itself, so they should Just Work™.
+---
-### Q: I am seeing a TypeScript error: `Type instantiation is excessively deep and possibly infinite`
+## External References
-A: This can often occur with deeply recursive types, which occur in this library. Please see [this
-comment](https://github.com/reduxjs/reselect/issues/534#issuecomment-956708953) for a discussion of the problem, as
-relating to nested selectors.
+Click to expand
-### Q: How can I make a [curried](https://github.com/hemanth/functional-programming-jargon#currying) selector?
+- [**`WeakMap`**][`WeakMap`]
+- [**`Reference Equality Check`**][Reference Equality Check]
+- [**`Memoization`**][Memoization]
+- [**`Identity Function`**][Identity Function]
-A: Try these [helper functions](https://github.com/reduxjs/reselect/issues/159#issuecomment-238724788) courtesy of [MattSPalmer](https://github.com/MattSPalmer)
+
## 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.
+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](https://github.com/skortchmark9/reselect-tools)
-[Chrome extension](https://chrome.google.com/webstore/detail/reselect-devtools/cjmaipngmabglflfeepmdiffcijhjlbb?hl=en) and [companion lib](https://github.com/skortchmark9/reselect-tools) for debugging selectors.
-
- 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
@@ -720,17 +1795,59 @@ Inspired by Reselect Tools, so it also has all functionality from this library a
- Selectors Output (In case if selector not dependent from external arguments)
- Shows "Not Memoized (NM)" selectors
+