reselect
Version:
Selectors for Redux.
455 lines (446 loc) • 14.8 kB
text/typescript
import { createSelector } from './createSelectorCreator'
import type { CreateSelectorFunction } from './createSelectorCreator'
import type {
InterruptRecursion,
ObjectValuesToTuple,
OutputSelector,
Selector,
Simplify,
UnknownMemoizer
} from './types'
import { assertIsObject } from './utils'
import type { weakMapMemoize } from './weakMapMemoize'
/**
* Represents a mapping of selectors to their return types.
*
* @template TObject - An object type where each property is a selector function.
*
* @public
*/
export type SelectorResultsMap<TObject extends SelectorsObject> = {
[Key in keyof TObject]: ReturnType<TObject[Key]>
}
/**
* Represents a mapping of selectors for each key in a given root state.
*
* This type is a utility that takes a root state object type and
* generates a corresponding set of selectors. Each selector is associated
* with a key in the root state, allowing for the selection
* of specific parts of the state.
*
* @template RootState - The type of the root state object.
*
* @since 5.0.0
* @public
*/
export type RootStateSelectors<RootState = any> = {
[Key in keyof RootState]: Selector<RootState, RootState[Key], []>
}
/**
* @deprecated Please use {@linkcode StructuredSelectorCreator.withTypes createStructuredSelector.withTypes<RootState>()} instead. This type will be removed in the future.
* @template RootState - The type of the root state object.
*
* @since 5.0.0
* @public
*/
export type TypedStructuredSelectorCreator<RootState = any> =
/**
* A convenience function that simplifies returning an object
* made up of selector results.
*
* @param inputSelectorsObject - A key value pair consisting of input selectors.
* @param selectorCreator - A custom selector creator function. It defaults to `createSelector`.
* @returns A memoized structured selector.
*
* @example
* <caption>Modern Use Case</caption>
* ```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
* }
* }
* )
* ```
*
* @example
* <caption>In your component:</caption>
* ```tsx
* import type { RootState } from 'createStructuredSelector/modernUseCase'
* import { structuredSelector } from 'createStructuredSelector/modernUseCase'
* import type { FC } from 'react'
* import { useSelector } from 'react-redux'
*
* interface Props {
* id: number
* }
*
* const MyComponent: FC<Props> = ({ id }) => {
* const { todos, alerts, todoById } = useSelector((state: RootState) =>
* structuredSelector(state, id)
* )
*
* return (
* <div>
* Next to do is:
* <h2>{todoById.title}</h2>
* <p>Description: {todoById.description}</p>
* <ul>
* <h3>All other to dos:</h3>
* {todos.map(todo => (
* <li key={todo.id}>{todo.title}</li>
* ))}
* </ul>
* </div>
* )
* }
* ```
*
* @example
* <caption>Simple Use Case</caption>
* ```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 }
* ```
*
* @template InputSelectorsObject - The shape of the input selectors object.
* @template MemoizeFunction - The type of the memoize function that is used to create the structured selector. It defaults to `weakMapMemoize`.
* @template ArgsMemoizeFunction - The type of the of the memoize function that is used to memoize the arguments passed into the generated structured selector. It defaults to `weakMapMemoize`.
*
* @see {@link https://reselect.js.org/api/createStructuredSelector `createStructuredSelector`}
*/
<
InputSelectorsObject extends RootStateSelectors<RootState> = RootStateSelectors<RootState>,
MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
>(
inputSelectorsObject: InputSelectorsObject,
selectorCreator?: CreateSelectorFunction<
MemoizeFunction,
ArgsMemoizeFunction
>
) => OutputSelector<
ObjectValuesToTuple<InputSelectorsObject>,
Simplify<SelectorResultsMap<InputSelectorsObject>>,
MemoizeFunction,
ArgsMemoizeFunction
> &
InterruptRecursion
/**
* Represents an object where each property is a selector function.
*
* @template StateType - The type of state that all the selectors operate on.
*
* @public
*/
export type SelectorsObject<StateType = any> = Record<
string,
Selector<StateType>
>
/**
* It provides a way to create structured selectors.
* The structured selector can take multiple input selectors
* and map their output to an object with specific keys.
*
* @template StateType - The type of state that the structured selectors created with this structured selector creator will operate on.
*
* @see {@link https://reselect.js.org/api/createStructuredSelector `createStructuredSelector`}
*
* @public
*/
export interface StructuredSelectorCreator<StateType = any> {
/**
* A convenience function that simplifies returning an object
* made up of selector results.
*
* @param inputSelectorsObject - A key value pair consisting of input selectors.
* @param selectorCreator - A custom selector creator function. It defaults to `createSelector`.
* @returns A memoized structured selector.
*
* @example
* <caption>Modern Use Case</caption>
* ```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
* }
* }
* )
* ```
*
* @example
* <caption>In your component:</caption>
* ```tsx
* import type { RootState } from 'createStructuredSelector/modernUseCase'
* import { structuredSelector } from 'createStructuredSelector/modernUseCase'
* import type { FC } from 'react'
* import { useSelector } from 'react-redux'
*
* interface Props {
* id: number
* }
*
* const MyComponent: FC<Props> = ({ id }) => {
* const { todos, alerts, todoById } = useSelector((state: RootState) =>
* structuredSelector(state, id)
* )
*
* return (
* <div>
* Next to do is:
* <h2>{todoById.title}</h2>
* <p>Description: {todoById.description}</p>
* <ul>
* <h3>All other to dos:</h3>
* {todos.map(todo => (
* <li key={todo.id}>{todo.title}</li>
* ))}
* </ul>
* </div>
* )
* }
* ```
*
* @example
* <caption>Simple Use Case</caption>
* ```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 }
* ```
*
* @template InputSelectorsObject - The shape of the input selectors object.
* @template MemoizeFunction - The type of the memoize function that is used to create the structured selector. It defaults to `weakMapMemoize`.
* @template ArgsMemoizeFunction - The type of the of the memoize function that is used to memoize the arguments passed into the generated structured selector. It defaults to `weakMapMemoize`.
*
* @see {@link https://reselect.js.org/api/createStructuredSelector `createStructuredSelector`}
*/
<
InputSelectorsObject extends SelectorsObject<StateType>,
MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
>(
inputSelectorsObject: InputSelectorsObject,
selectorCreator?: CreateSelectorFunction<
MemoizeFunction,
ArgsMemoizeFunction
>
): OutputSelector<
ObjectValuesToTuple<InputSelectorsObject>,
Simplify<SelectorResultsMap<InputSelectorsObject>>,
MemoizeFunction,
ArgsMemoizeFunction
> &
InterruptRecursion
/**
* Creates a "pre-typed" version of
* {@linkcode createStructuredSelector createStructuredSelector}
* where the `state` type is predefined.
*
* This allows you to set the `state` type once, eliminating the need to
* specify it with every
* {@linkcode createStructuredSelector createStructuredSelector} call.
*
* @returns A pre-typed `createStructuredSelector` with the state type already defined.
*
* @example
* ```ts
* import { createStructuredSelector } from 'reselect'
*
* export interface RootState {
* todos: { id: number; completed: boolean }[]
* alerts: { id: number; read: boolean }[]
* }
*
* export const createStructuredAppSelector =
* createStructuredSelector.withTypes<RootState>()
*
* const structuredAppSelector = createStructuredAppSelector({
* // Type of `state` is set to `RootState`, no need to manually set the type
* todos: state => state.todos,
* alerts: state => state.alerts,
* todoById: (state, id: number) => state.todos[id]
* })
*
* ```
* @template OverrideStateType - The specific type of state used by all structured selectors created with this structured selector creator.
*
* @see {@link https://reselect.js.org/api/createstructuredselector#defining-a-pre-typed-createstructuredselector `createSelector.withTypes`}
*
* @since 5.1.0
*/
withTypes: <
OverrideStateType extends StateType
>() => StructuredSelectorCreator<OverrideStateType>
}
/**
* A convenience function that simplifies returning an object
* made up of selector results.
*
* @param inputSelectorsObject - A key value pair consisting of input selectors.
* @param selectorCreator - A custom selector creator function. It defaults to `createSelector`.
* @returns A memoized structured selector.
*
* @example
* <caption>Modern Use Case</caption>
* ```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
* }
* }
* )
* ```
*
* @see {@link https://reselect.js.org/api/createStructuredSelector `createStructuredSelector`}
*
* @public
*/
export const createStructuredSelector: StructuredSelectorCreator =
Object.assign(
<
InputSelectorsObject extends SelectorsObject,
MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
>(
inputSelectorsObject: InputSelectorsObject,
selectorCreator: CreateSelectorFunction<
MemoizeFunction,
ArgsMemoizeFunction
> = createSelector as CreateSelectorFunction<
MemoizeFunction,
ArgsMemoizeFunction
>
) => {
assertIsObject(
inputSelectorsObject,
'createStructuredSelector expects first argument to be an object ' +
`where each property is a selector, instead received a ${typeof inputSelectorsObject}`
)
const inputSelectorKeys = Object.keys(inputSelectorsObject)
const dependencies = inputSelectorKeys.map(
key => inputSelectorsObject[key]
)
const structuredSelector = selectorCreator(
dependencies,
(...inputSelectorResults: any[]) => {
return inputSelectorResults.reduce((composition, value, index) => {
composition[inputSelectorKeys[index]] = value
return composition
}, {})
}
)
return structuredSelector
},
{ withTypes: () => createStructuredSelector }
) as StructuredSelectorCreator