Skip to main content

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:

FAQ/selectorRecomputing.ts
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
}
}
}
)

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:

  1. Consistency in Arguments: Ensure that all positional arguments across input selectors are of the same type for consistency.

  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.

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.

FAQ/howToTest.test.ts
import { createSelector } from 'reselect'
import { expect, test } from 'vitest'

interface RootState {
todos: { id: number; completed: boolean }[]
alerts: { id: number; read: boolean }[]
}

const state: RootState = {
todos: [
{ id: 0, completed: false },
{ id: 1, completed: true }
],
alerts: [
{ id: 0, read: false },
{ id: 1, read: true }
]
}

// With `Vitest` or `Jest`
test('selector unit test', () => {
const selectTodoIds = createSelector(
[(state: RootState) => state.todos],
todos => todos.map(({ id }) => id)
)
const firstResult = selectTodoIds(state)
const secondResult = selectTodoIds(state)
// Reference equality should pass.
expect(firstResult).toBe(secondResult)
// Deep equality should also pass.
expect(firstResult).toStrictEqual(secondResult)
selectTodoIds(state)
selectTodoIds(state)
selectTodoIds(state)
// The result function should not recalculate.
expect(selectTodoIds.recomputations()).toBe(1)
// input selectors should not recalculate.
expect(selectTodoIds.dependencyRecomputations()).toBe(1)
})

// With `Chai`
test('selector unit test', () => {
const selectTodoIds = createSelector(
[(state: RootState) => state.todos],
todos => todos.map(({ id }) => id)
)
const firstResult = selectTodoIds(state)
const secondResult = selectTodoIds(state)
// Reference equality should pass.
expect(firstResult).to.equal(secondResult)
// Deep equality should also pass.
expect(firstResult).to.deep.equal(secondResult)
selectTodoIds(state)
selectTodoIds(state)
selectTodoIds(state)
// The result function should not recalculate.
expect(selectTodoIds.recomputations()).to.equal(1)
// input selectors should not recalculate.
expect(selectTodoIds.dependencyRecomputations()).to.equal(1)
})

Can I share a selector across multiple component instances?

Yes, although if they pass in different arguments, you will need to handle that in order for memoization to work consistently:

  • Pass a larger maxSize if using lruMemoize ( as of 4.1.0+)
  • Use weakMapMemoize (as of 5.0.0+)

Are there TypeScript Typings?

Yes! Reselect is now written in TypeScript itself, so they should Just Work™.

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:

FAQ/currySelector.ts
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)
)
)

Or for reusability you can do this:

FAQ/createCurriedSelector.ts
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))
}

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:

FAQ/createParametricSelectorHook.ts
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)

And then inside your component:

FAQ/MyComponent.tsx
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>
}

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:

FAQ/identity.ts
import { createSelectorCreator } from 'reselect'

const identity = <Func extends (...args: any[]) => any>(func: Func) => func

const createNonMemoizedSelector = createSelectorCreator({
memoize: identity,
argsMemoize: identity
})