diff --git a/README.md b/README.md index c0f9b973b..877066feb 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,6 @@ Version 5.0.0 introduces several new features and improvements: - **Selector API Enhancements**: - Removed the second overload of `createStructuredSelector` due to its susceptibility to runtime errors. - - Added the `TypedStructuredSelectorCreator` utility type (_currently a work-in-progress_) to facilitate the creation of a pre-typed version of `createStructuredSelector` for your root state. - **Additional Functionalities**: diff --git a/docs/examples/createStructuredSelector/createStructuredAppSelector.ts b/docs/examples/createStructuredSelector/createStructuredAppSelector.ts deleted file mode 100644 index ae17e1564..000000000 --- a/docs/examples/createStructuredSelector/createStructuredAppSelector.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { TypedStructuredSelectorCreator } from 'reselect' -import { createStructuredSelector } from 'reselect' - -interface RootState { - todos: { - id: number - completed: boolean - title: string - description: string - }[] - alerts: { id: number; read: boolean }[] -} - -export const createStructuredAppSelector: TypedStructuredSelectorCreator = - createStructuredSelector - -const structuredSelector = createStructuredAppSelector({ - // The `state` argument is correctly typed as `RootState` - todos: state => state.todos, - alerts: state => state.alerts -}) diff --git a/docs/examples/createStructuredSelector/withTypes.ts b/docs/examples/createStructuredSelector/withTypes.ts new file mode 100644 index 000000000..d0b083120 --- /dev/null +++ b/docs/examples/createStructuredSelector/withTypes.ts @@ -0,0 +1,18 @@ +import { createStructuredSelector } from 'reselect' + +export interface RootState { + todos: { id: number; completed: boolean }[] + alerts: { id: number; read: boolean }[] +} + +export const createStructuredAppSelector = + createStructuredSelector.withTypes() + +const structuredAppSelector = createStructuredAppSelector({ + // Type of `state` is set to `RootState`, no need to manually set the type + // highlight-start + todos: state => state.todos, + // highlight-end + alerts: state => state.alerts, + todoById: (state, id: number) => state.todos[id] +}) diff --git a/package.json b/package.json index 2c41eda8e..baa9394cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reselect", - "version": "5.0.1", + "version": "5.0.2", "description": "Selectors for Redux.", "main": "./dist/cjs/reselect.cjs", "module": "./dist/reselect.legacy-esm.js", diff --git a/src/createSelectorCreator.ts b/src/createSelectorCreator.ts index aed72dd34..ac2238fac 100644 --- a/src/createSelectorCreator.ts +++ b/src/createSelectorCreator.ts @@ -176,7 +176,7 @@ export interface CreateSelectorFunction< * * @since 5.0.2 */ - withTypes(): CreateSelectorFunction< + withTypes: () => CreateSelectorFunction< MemoizeFunction, ArgsMemoizeFunction, OverrideStateType diff --git a/src/createStructuredSelector.ts b/src/createStructuredSelector.ts index a16e395a1..794f311c9 100644 --- a/src/createStructuredSelector.ts +++ b/src/createStructuredSelector.ts @@ -41,40 +41,9 @@ export type RootStateSelectors = { } /** - * Allows you to create a pre-typed version of - * {@linkcode createStructuredSelector createStructuredSelector} - * tailored to the provided root state type. - * - * @example - * ```ts - * import type { TypedStructuredSelectorCreator } from 'reselect' - * import { createStructuredSelector } from 'reselect' - * - * interface RootState { - * todos: { - * id: number - * completed: boolean - * title: string - * description: string - * }[] - * alerts: { id: number; read: boolean }[] - * } - * - * export const createStructuredAppSelector: TypedStructuredSelectorCreator = - * createStructuredSelector - * - * const structuredSelector = createStructuredAppSelector({ - * // The `state` argument is correctly typed as `RootState` - * todos: state => state.todos, - * alerts: state => state.alerts - * }) - * - * ``` - * + * @deprecated Please use {@linkcode StructuredSelectorCreator.withTypes createStructuredSelector.withTypes()} instead. This type will be removed in the future. * @template RootState - The type of the root state object. * - * @see {@link https://reselect.js.org/api/createStructuredSelector#typedstructuredselectorcreator-since-500 `TypedStructuredSelectorCreator`} - * * @since 5.0.0 * @public */ @@ -205,22 +174,27 @@ export type TypedStructuredSelectorCreator = /** * Represents an object where each property is a selector function. * + * @template StateType - The type of state that all the selectors operate on. + * * @public */ -export interface SelectorsObject { - [key: string]: Selector -} +export type SelectorsObject = Record< + string, + Selector +> /** * 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 type StructuredSelectorCreator = +export interface StructuredSelectorCreator { /** * A convenience function that simplifies returning an object * made up of selector results. @@ -327,7 +301,7 @@ export type StructuredSelectorCreator = * @see {@link https://reselect.js.org/api/createStructuredSelector `createStructuredSelector`} */ < - InputSelectorsObject extends SelectorsObject, + InputSelectorsObject extends SelectorsObject, MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize, ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize >( @@ -336,7 +310,7 @@ export type StructuredSelectorCreator = MemoizeFunction, ArgsMemoizeFunction > - ) => OutputSelector< + ): OutputSelector< ObjectValuesToTuple, Simplify>, MemoizeFunction, @@ -344,10 +318,56 @@ export type StructuredSelectorCreator = > & 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() + * + * 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.0.2 + */ + withTypes: < + OverrideStateType extends StateType + >() => StructuredSelectorCreator +} + /** * 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 * Modern Use Case * ```ts @@ -394,35 +414,41 @@ export type StructuredSelectorCreator = * * @public */ -export const createStructuredSelector: StructuredSelectorCreator = (< - 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 -}) as StructuredSelectorCreator +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 diff --git a/test/createStructuredSelector.withTypes.test.ts b/test/createStructuredSelector.withTypes.test.ts new file mode 100644 index 000000000..bcdb30816 --- /dev/null +++ b/test/createStructuredSelector.withTypes.test.ts @@ -0,0 +1,27 @@ +import { createStructuredSelector } from 'reselect' +import type { RootState } from './testUtils' +import { localTest } from './testUtils' + +describe(createStructuredSelector.withTypes, () => { + const createTypedStructuredSelector = + createStructuredSelector.withTypes() + + localTest('should return createStructuredSelector', ({ state }) => { + expect(createTypedStructuredSelector.withTypes).to.be.a('function') + + expect(createTypedStructuredSelector.withTypes().withTypes).to.be.a( + 'function' + ) + + expect(createTypedStructuredSelector).toBe(createStructuredSelector) + + const structuredSelector = createTypedStructuredSelector({ + todos: state => state.todos, + alerts: state => state.alerts + }) + + expect(structuredSelector).toBeMemoizedSelector() + + expect(structuredSelector(state)).to.be.an('object').that.is.not.empty + }) +}) diff --git a/type-tests/createStructuredSelector.test-d.ts b/type-tests/createStructuredSelector.test-d.ts index 99833a8f2..864cb44f2 100644 --- a/type-tests/createStructuredSelector.test-d.ts +++ b/type-tests/createStructuredSelector.test-d.ts @@ -35,6 +35,8 @@ const rootState: RootState = { } describe('createStructuredSelector', () => { + + // TODO: Remove this test block once `TypedStructuredSelectorCreator` is removed. test('TypedStructuredSelectorCreator should lock down state type', () => { const createStructuredAppSelector: TypedStructuredSelectorCreator = createStructuredSelector @@ -112,6 +114,7 @@ describe('createStructuredSelector', () => { >(structuredSelector.lastResult()) }) + // TODO: Remove this test block once `TypedStructuredSelectorCreator` is removed. test('TypedStructuredSelectorCreator should correctly infer memoize and argsMemoize', () => { const createSelectorLru = createSelectorCreator({ memoize: lruMemoize, diff --git a/type-tests/createStructuredSelector.withTypes.test-d.ts b/type-tests/createStructuredSelector.withTypes.test-d.ts new file mode 100644 index 000000000..9bb5b1ff7 --- /dev/null +++ b/type-tests/createStructuredSelector.withTypes.test-d.ts @@ -0,0 +1,543 @@ +import microMemoize from 'micro-memoize' +import type { + Selector, + StructuredSelectorCreator, + TypedStructuredSelectorCreator +} from 'reselect' +import { + createSelector, + createSelectorCreator, + createStructuredSelector, + lruMemoize, + weakMapMemoize +} from 'reselect' +import { describe, expectTypeOf, test } from 'vitest' + +interface Todo { + id: number + completed: boolean +} + +interface Alert { + id: number + read: boolean +} + +interface RootState { + todos: Todo[] + alerts: Alert[] +} + +const rootState: RootState = { + todos: [ + { id: 0, completed: false }, + { id: 1, completed: false } + ], + alerts: [ + { id: 0, read: false }, + { id: 1, read: false } + ] +} + +describe('createStructuredSelector.withTypes()', () => { + const createStructuredAppSelector = + createStructuredSelector.withTypes() + + test('locks down state type and infers types correctly', () => { + expectTypeOf(createStructuredSelector.withTypes).returns.toEqualTypeOf( + createStructuredSelector + ) + + const structuredAppSelector = createStructuredAppSelector({ + todos: state => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.todos + }, + alerts: state => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.alerts + } + }) + + const { todos, alerts } = structuredAppSelector(rootState) + + expectTypeOf(todos).toEqualTypeOf() + + expectTypeOf(alerts).toEqualTypeOf() + + expectTypeOf(structuredAppSelector.argsMemoize).toEqualTypeOf< + typeof weakMapMemoize + >(weakMapMemoize) + + expectTypeOf(structuredAppSelector.memoize).toEqualTypeOf< + typeof weakMapMemoize + >(weakMapMemoize) + + expectTypeOf(structuredAppSelector.clearCache).returns.toBeVoid() + + expectTypeOf(structuredAppSelector.clearCache).parameters.toEqualTypeOf< + [] + >() + + expectTypeOf(structuredAppSelector.dependencies).items.toBeFunction() + + expectTypeOf(structuredAppSelector.dependencyRecomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf(structuredAppSelector.recomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf( + structuredAppSelector.resetDependencyRecomputations + ).toEqualTypeOf<() => void>() + + expectTypeOf(structuredAppSelector.resetRecomputations).toEqualTypeOf< + () => void + >() + + expectTypeOf( + structuredAppSelector.lastResult + ).returns.toEqualTypeOf(rootState) + + expectTypeOf( + structuredAppSelector.memoizedResultFunc + ).parameters.toEqualTypeOf<[Todo[], Alert[]]>([ + rootState.todos, + rootState.alerts + ]) + + expectTypeOf( + structuredAppSelector.memoizedResultFunc + ).returns.toEqualTypeOf< + ReturnType + >(structuredAppSelector.lastResult()) + + expectTypeOf(structuredAppSelector.memoizedResultFunc).toHaveProperty( + 'clearCache' + ) + + expectTypeOf(structuredAppSelector.resultFunc).returns.toEqualTypeOf< + ReturnType + >(structuredAppSelector.lastResult()) + }) + + test('should correctly infer memoize and argsMemoize', () => { + const createSelectorLru = createSelectorCreator({ + memoize: lruMemoize, + argsMemoize: microMemoize + }) + + const structuredSelector = createStructuredAppSelector( + { + todos: state => state.todos, + alerts: state => state.alerts + }, + createSelectorLru + ) + + expectTypeOf(structuredSelector.argsMemoize).toEqualTypeOf< + typeof microMemoize + >(microMemoize) + + expectTypeOf(structuredSelector.memoize).toEqualTypeOf( + lruMemoize + ) + + const { todos, alerts } = structuredSelector(rootState) + + expectTypeOf(todos).toEqualTypeOf() + + expectTypeOf(alerts).toEqualTypeOf() + + expectTypeOf(structuredSelector.dependencies).items.toBeFunction() + + expectTypeOf(structuredSelector.dependencyRecomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf(structuredSelector.recomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf( + structuredSelector.resetDependencyRecomputations + ).toEqualTypeOf<() => void>() + + expectTypeOf(structuredSelector.resetRecomputations).toEqualTypeOf< + () => void + >() + + expectTypeOf( + structuredSelector.lastResult + ).returns.toEqualTypeOf(rootState) + + expectTypeOf( + structuredSelector.memoizedResultFunc + ).parameters.toEqualTypeOf<[Todo[], Alert[]]>([ + rootState.todos, + rootState.alerts + ]) + + expectTypeOf(structuredSelector.memoizedResultFunc).returns.toEqualTypeOf< + ReturnType + >(structuredSelector.lastResult()) + + expectTypeOf(structuredSelector.memoizedResultFunc).toHaveProperty( + 'clearCache' + ) + + expectTypeOf(structuredSelector.resultFunc).returns.toEqualTypeOf< + ReturnType + >(structuredSelector.lastResult()) + }) + + test('supports additional parameters', () => { + const structuredAppSelector = createStructuredAppSelector({ + todos: state => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.todos + }, + alerts: state => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.alerts + }, + todoById: (state, id: number) => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.todos[id] + } + }) + + const { alerts, todos, todoById } = structuredAppSelector(rootState, 0) + + expectTypeOf(todos).toEqualTypeOf() + + expectTypeOf(alerts).toEqualTypeOf() + + expectTypeOf(todoById).toEqualTypeOf() + + expectTypeOf(structuredAppSelector.argsMemoize).toEqualTypeOf< + typeof weakMapMemoize + >(weakMapMemoize) + + expectTypeOf(structuredAppSelector.memoize).toEqualTypeOf< + typeof weakMapMemoize + >(weakMapMemoize) + + expectTypeOf(structuredAppSelector.clearCache).returns.toBeVoid() + + expectTypeOf(structuredAppSelector.clearCache).parameters.toEqualTypeOf< + [] + >() + + expectTypeOf(structuredAppSelector.dependencies).items.toMatchTypeOf< + Selector + >() + + expectTypeOf(structuredAppSelector.dependencyRecomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf(structuredAppSelector.recomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf( + structuredAppSelector.resetDependencyRecomputations + ).returns.toBeVoid() + + expectTypeOf( + structuredAppSelector.resetDependencyRecomputations + ).parameters.items.toBeNever() + + expectTypeOf(structuredAppSelector.resetRecomputations).returns.toBeVoid() + + expectTypeOf( + structuredAppSelector.resetRecomputations + ).parameters.items.toBeNever() + + // Use `.branded` for intersection types https://github.com/mmkal/expect-type#why-is-my-assertion-failing + expectTypeOf( + structuredAppSelector.lastResult + ).returns.branded.toEqualTypeOf() + + expectTypeOf( + structuredAppSelector.memoizedResultFunc + ).parameters.toEqualTypeOf<[Todo[], Alert[], Todo]>([ + rootState.todos, + rootState.alerts, + rootState.todos[0] + ]) + + expectTypeOf(structuredAppSelector.resultFunc).parameters.toEqualTypeOf< + [Todo[], Alert[], Todo] + >([rootState.todos, rootState.alerts, rootState.todos[0]]) + + expectTypeOf( + structuredAppSelector.memoizedResultFunc + ).returns.toEqualTypeOf< + ReturnType + >(structuredAppSelector.lastResult()) + + expectTypeOf(structuredAppSelector.memoizedResultFunc).toHaveProperty( + 'clearCache' + ) + + expectTypeOf(structuredAppSelector.resultFunc).returns.toEqualTypeOf< + ReturnType + >(structuredAppSelector.lastResult()) + }) + + // TODO: Remove this test block once `TypedStructuredSelectorCreator` is removed. + test('should work alongside TypedStructuredSelectorCreator', () => { + const createStructuredAppSelector: TypedStructuredSelectorCreator = + createStructuredSelector.withTypes() + + const structuredAppSelector = createStructuredAppSelector({ + todos: state => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.todos + }, + alerts: state => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.alerts + } + }) + + const { todos, alerts } = structuredAppSelector(rootState) + + expectTypeOf(todos).toEqualTypeOf() + + expectTypeOf(alerts).toEqualTypeOf() + + expectTypeOf(structuredAppSelector.argsMemoize).toEqualTypeOf< + typeof weakMapMemoize + >(weakMapMemoize) + + expectTypeOf(structuredAppSelector.memoize).toEqualTypeOf< + typeof weakMapMemoize + >(weakMapMemoize) + + expectTypeOf(structuredAppSelector.clearCache).returns.toBeVoid() + + expectTypeOf(structuredAppSelector.clearCache).parameters.toEqualTypeOf< + [] + >() + + expectTypeOf(structuredAppSelector.dependencies).items.toBeFunction() + + expectTypeOf(structuredAppSelector.dependencyRecomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf(structuredAppSelector.recomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf( + structuredAppSelector.resetDependencyRecomputations + ).toEqualTypeOf<() => void>() + + expectTypeOf(structuredAppSelector.resetRecomputations).toEqualTypeOf< + () => void + >() + + expectTypeOf( + structuredAppSelector.lastResult + ).returns.toEqualTypeOf(rootState) + + expectTypeOf( + structuredAppSelector.memoizedResultFunc + ).parameters.toEqualTypeOf<[Todo[], Alert[]]>([ + rootState.todos, + rootState.alerts + ]) + + expectTypeOf( + structuredAppSelector.memoizedResultFunc + ).returns.toEqualTypeOf< + ReturnType + >(structuredAppSelector.lastResult()) + + expectTypeOf(structuredAppSelector.memoizedResultFunc).toHaveProperty( + 'clearCache' + ) + + expectTypeOf(structuredAppSelector.resultFunc).returns.toEqualTypeOf< + ReturnType + >(structuredAppSelector.lastResult()) + }) + + test('should work with createSelector.withTypes()', () => { + const structuredAppSelector = createStructuredAppSelector( + { + todos: state => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.todos + }, + alerts: state => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.alerts + } + }, + createSelector.withTypes() + ) + + const { todos, alerts } = structuredAppSelector(rootState) + + expectTypeOf(todos).toEqualTypeOf() + + expectTypeOf(alerts).toEqualTypeOf() + + expectTypeOf(structuredAppSelector.argsMemoize).toEqualTypeOf< + typeof weakMapMemoize + >(weakMapMemoize) + + expectTypeOf(structuredAppSelector.memoize).toEqualTypeOf< + typeof weakMapMemoize + >(weakMapMemoize) + + expectTypeOf(structuredAppSelector.clearCache).returns.toBeVoid() + + expectTypeOf(structuredAppSelector.clearCache).parameters.toEqualTypeOf< + [] + >() + + expectTypeOf(structuredAppSelector.dependencies).items.toBeFunction() + + expectTypeOf(structuredAppSelector.dependencyRecomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf(structuredAppSelector.recomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf( + structuredAppSelector.resetDependencyRecomputations + ).toEqualTypeOf<() => void>() + + expectTypeOf(structuredAppSelector.resetRecomputations).toEqualTypeOf< + () => void + >() + + expectTypeOf( + structuredAppSelector.lastResult + ).returns.toEqualTypeOf(rootState) + + expectTypeOf( + structuredAppSelector.memoizedResultFunc + ).parameters.toEqualTypeOf<[Todo[], Alert[]]>([ + rootState.todos, + rootState.alerts + ]) + + expectTypeOf( + structuredAppSelector.memoizedResultFunc + ).returns.toEqualTypeOf< + ReturnType + >(structuredAppSelector.lastResult()) + + expectTypeOf(structuredAppSelector.memoizedResultFunc).toHaveProperty( + 'clearCache' + ) + + expectTypeOf(structuredAppSelector.resultFunc).returns.toEqualTypeOf< + ReturnType + >(structuredAppSelector.lastResult()) + }) + + test('StructuredSelectorCreator should lock down the state type', () => { + const createStructuredAppSelector: StructuredSelectorCreator = + createStructuredSelector + + const structuredAppSelector = createStructuredAppSelector( + { + todos: state => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.todos + }, + alerts: state => { + expectTypeOf(state).toEqualTypeOf(rootState) + + return state.alerts + } + }, + createSelector.withTypes() + ) + + const { todos, alerts } = structuredAppSelector(rootState) + + expectTypeOf(todos).toEqualTypeOf() + + expectTypeOf(alerts).toEqualTypeOf() + + expectTypeOf(structuredAppSelector.argsMemoize).toEqualTypeOf< + typeof weakMapMemoize + >(weakMapMemoize) + + expectTypeOf(structuredAppSelector.memoize).toEqualTypeOf< + typeof weakMapMemoize + >(weakMapMemoize) + + expectTypeOf(structuredAppSelector.clearCache).returns.toBeVoid() + + expectTypeOf(structuredAppSelector.clearCache).parameters.toEqualTypeOf< + [] + >() + + expectTypeOf(structuredAppSelector.dependencies).items.toBeFunction() + + expectTypeOf(structuredAppSelector.dependencyRecomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf(structuredAppSelector.recomputations).toEqualTypeOf< + () => number + >() + + expectTypeOf( + structuredAppSelector.resetDependencyRecomputations + ).toEqualTypeOf<() => void>() + + expectTypeOf(structuredAppSelector.resetRecomputations).toEqualTypeOf< + () => void + >() + + expectTypeOf( + structuredAppSelector.lastResult + ).returns.toEqualTypeOf(rootState) + + expectTypeOf( + structuredAppSelector.memoizedResultFunc + ).parameters.toEqualTypeOf<[Todo[], Alert[]]>([ + rootState.todos, + rootState.alerts + ]) + + expectTypeOf( + structuredAppSelector.memoizedResultFunc + ).returns.toEqualTypeOf< + ReturnType + >(structuredAppSelector.lastResult()) + + expectTypeOf(structuredAppSelector.memoizedResultFunc).toHaveProperty( + 'clearCache' + ) + + expectTypeOf(structuredAppSelector.resultFunc).returns.toEqualTypeOf< + ReturnType + >(structuredAppSelector.lastResult()) + }) +}) diff --git a/typescript_test/test.ts b/typescript_test/test.ts index c58547228..0795f2e70 100644 --- a/typescript_test/test.ts +++ b/typescript_test/test.ts @@ -17,8 +17,8 @@ import { createSelector, createSelectorCreator, createStructuredSelector, - referenceEqualityCheck, - lruMemoize + lruMemoize, + referenceEqualityCheck } from 'reselect' import { expectExactType } from './typesTestUtils' @@ -748,8 +748,8 @@ function testCreateStructuredSelector() { bar: number } - const typedStructuredSelectorCreator: TypedStructuredSelectorCreator = - createStructuredSelector + const typedStructuredSelectorCreator = + createStructuredSelector.withTypes() const selector = typedStructuredSelectorCreator({ foo: state => state.foo, @@ -772,12 +772,10 @@ function testCreateStructuredSelector() { }) typedStructuredSelectorCreator({ - // @ts-expect-error bar: state => state.foo }) typedStructuredSelectorCreator({ - // @ts-expect-error baz: state => state.foo }) @@ -820,6 +818,7 @@ function testCreateStructuredSelector() { selectorGenerics({ bar: '42' }) } +// TODO: Remove this function once `TypedStructuredSelectorCreator` is removed. function testTypedCreateStructuredSelector() { type RootState = { foo: string @@ -921,20 +920,6 @@ function testStructuredSelectorTypeParams() { // bar: selectBar, // ^^^ because this is missing, an error is thrown }) - - const typedStructuredSelectorCreator: TypedStructuredSelectorCreator = - createStructuredSelector - - // This works - typedStructuredSelectorCreator({ - foo: selectFoo, - bar: selectBar - }) - - // // So does this - // typedStructuredSelectorCreator>({ - // foo: selectFoo - // }) } function multiArgMemoize any>( diff --git a/website/docs/api/createSelectorCreator.mdx b/website/docs/api/createSelectorCreator.mdx index 6f5886b2c..65908b1e9 100644 --- a/website/docs/api/createSelectorCreator.mdx +++ b/website/docs/api/createSelectorCreator.mdx @@ -17,7 +17,7 @@ Accepts either a `memoize` function and `...memoizeOptions` rest parameter, or s | Name | Description | | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `options` | An options object containing the `memoize` function responsible for memoizing the `resultFunc` inside (e.g., `lruMemoize` or `weakMapMemoize`). It also provides additional options for customizing memoization. While the `memoize` property is mandatory, the rest are optional. | -| `options.argsMemoize?` | The optional memoize function that is used to memoize the arguments passed into the generated by (e.g., `lruMemoize` or `weakMapMemoize`).
**`Default`** = `lruMemoize` before 5.0.0 and `weakMapMemoize` after | +| `options.argsMemoize?` | The optional memoize function that is used to memoize the arguments passed into the generated by (e.g., `lruMemoize` or `weakMapMemoize`).
**`Default`** = `lruMemoize` before 5.0.0 and `weakMapMemoize` after | | `options.argsMemoizeOptions?` | Optional configuration options for the `argsMemoize` function. These options are passed to the `argsMemoize` function as the second argument.
since 5.0.0 | | `options.inputStabilityCheck?` | Overrides the global input stability check for the selector. Possible values are:
`once` - Run only the first time the selector is called.
`always` - Run every time the selector is called.
`never` - Never run the input stability check.
**`Default`** = `'once'`
since 5.0.0 | | `options.memoize` | The memoize function that is used to memoize the `resultFunc` inside (e.g., `lruMemoize` or `weakMapMemoize`). since 5.0.0 | diff --git a/website/docs/api/createStructuredSelector.mdx b/website/docs/api/createStructuredSelector.mdx index d12b1e096..bf0faea8a 100644 --- a/website/docs/api/createStructuredSelector.mdx +++ b/website/docs/api/createStructuredSelector.mdx @@ -244,11 +244,13 @@ const nestedSelector = createStructuredSelector({ }) ``` -## `TypedStructuredSelectorCreator` (since 5.0.0) +## Defining a Pre-Typed `createStructuredSelector` -A helper type that allows you to create a pre-typed version of tailored to the provided root state type. +As of Reselect 5.0.2, you can create a "pre-typed" version of where the `state` type is predefined. This allows you to set the `state` type once, eliminating the need to specify it with every call. - +To do this, you can call `createStructuredSelector.withTypes()`: + +{/* START: createStructuredSelector/withTypes.ts */} -```ts title="createStructuredSelector/createStructuredAppSelector.ts" -import type { TypedStructuredSelectorCreator } from 'reselect' +```ts title="createStructuredSelector/withTypes.ts" import { createStructuredSelector } from 'reselect' -interface RootState { - todos: { - id: number - completed: boolean - title: string - description: string - }[] +export interface RootState { + todos: { id: number; completed: boolean }[] alerts: { id: number; read: boolean }[] } -export const createStructuredAppSelector: TypedStructuredSelectorCreator = - createStructuredSelector +export const createStructuredAppSelector = + createStructuredSelector.withTypes() -const structuredSelector = createStructuredAppSelector({ - // The `state` argument is correctly typed as `RootState` +const structuredAppSelector = createStructuredAppSelector({ + // Type of `state` is set to `RootState`, no need to manually set the type + // highlight-start todos: state => state.todos, - alerts: state => state.alerts + // highlight-end + alerts: state => state.alerts, + todoById: (state, id: number) => state.todos[id] }) ``` -```js title="createStructuredSelector/createStructuredAppSelector.js" +```js title="createStructuredSelector/withTypes.js" import { createStructuredSelector } from 'reselect' -export const createStructuredAppSelector = createStructuredSelector +export const createStructuredAppSelector = createStructuredSelector.withTypes() -const structuredSelector = createStructuredAppSelector({ - // The `state` argument is correctly typed as `RootState` +const structuredAppSelector = createStructuredAppSelector({ + // Type of `state` is set to `RootState`, no need to manually set the type + // highlight-start todos: state => state.todos, - alerts: state => state.alerts + // highlight-end + alerts: state => state.alerts, + todoById: (state, id) => state.todos[id] }) ``` - +{/* END: createStructuredSelector/withTypes.ts */} diff --git a/website/docs/introduction/v5-summary.mdx b/website/docs/introduction/v5-summary.mdx index 28bbf7c3a..9651885fe 100644 --- a/website/docs/introduction/v5-summary.mdx +++ b/website/docs/introduction/v5-summary.mdx @@ -31,7 +31,6 @@ Version 5.0.0 introduces several new features and improvements: ## Selector API Enhancements - Removed the second overload of due to its susceptibility to runtime errors. -- Added the utility type to facilitate the creation of a pre-typed version of for your root state. ## Additional Functionalities diff --git a/website/src/components/InternalLinks.tsx b/website/src/components/InternalLinks.tsx index 462778df2..4c30b2427 100644 --- a/website/src/components/InternalLinks.tsx +++ b/website/src/components/InternalLinks.tsx @@ -89,13 +89,5 @@ export const InternalLinks = { createStructuredSelector - )), - TypedStructuredSelectorCreator: memo(() => ( - - TypedStructuredSelectorCreator - )) } as const satisfies Record>