Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce pre-typed createStructuredSelector via createStructuredSelector.withTypes<RootState>() method #678

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b4439a9
Add initial implementation of `createStructuredSelector.withTypes`
aryaemami59 Jan 4, 2024
0f6bb10
Add type tests for `createStructuredSelector.withTypes`
aryaemami59 Jan 4, 2024
d3464d8
Add runtime unit tests for `createStructuredSelector.withTypes`
aryaemami59 Jan 4, 2024
605e46a
Add additional type tests for `createStructuredSelector.withTypes`
aryaemami59 Jan 4, 2024
d9fcb15
Add type tests for `StructuredSelectorCreator`
aryaemami59 Jan 4, 2024
e7f39ca
Change `SelectorsObject` from interface with index signature to `Reco…
aryaemami59 Jan 4, 2024
c0f7c8a
Change `createStructuredSelector.withTypes` type to arrow function
aryaemami59 Jan 4, 2024
68a119b
Change `createSelector.withTypes` type to arrow function
aryaemami59 Jan 4, 2024
37ebaf8
Add `createStructuredSelector.withTypes` example for docs
aryaemami59 Jan 4, 2024
986ca8a
Add JSDocs for `StateType` generic type parameter in `StructuredSelec…
aryaemami59 Jan 4, 2024
121fc36
Add JSDocs for `StateType` generic type parameter in `SelectorsObject`
aryaemami59 Jan 4, 2024
25544d7
Add JSDocs for `createStructuredSelector.withTypes`
aryaemami59 Jan 4, 2024
8b93622
Fix typos inside docs
aryaemami59 Jan 4, 2024
4cc0883
Deprecate `TypedStructuredSelectorCreator`
aryaemami59 Jan 4, 2024
3e39fcc
Fix typos inside docs
aryaemami59 Jan 4, 2024
7f1588c
Modify JSDocs for `TypedStructuredSelectorCreator`
aryaemami59 Jan 5, 2024
9b4b7ac
Add todo comments to remove certain test blocks
aryaemami59 Jan 5, 2024
73e45f7
Remove `TypedStructuredSelectorCreator` from `README`
aryaemami59 Jan 5, 2024
140f38f
Remove `createStructuredAppSelector.ts` from examples
aryaemami59 Jan 5, 2024
025c1a4
Remove `TypedStructuredSelectorCreator` from docs
aryaemami59 Jan 5, 2024
1368970
Change version to 5.0.2 to workaround CI issue
aryaemami59 Jan 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**:

Expand Down

This file was deleted.

18 changes: 18 additions & 0 deletions docs/examples/createStructuredSelector/withTypes.ts
Original file line number Diff line number Diff line change
@@ -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<RootState>()

const structuredAppSelector = createStructuredAppSelector({
// Type of `state` is set to `RootState`, no need to manually set the type
// highlight-start
todos: state => state.todos,
// highlight-end
alerts: state => state.alerts,
todoById: (state, id: number) => state.todos[id]
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/createSelectorCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export interface CreateSelectorFunction<
*
* @since 5.0.2
*/
withTypes<OverrideStateType extends StateType>(): CreateSelectorFunction<
withTypes: <OverrideStateType extends StateType>() => CreateSelectorFunction<
MemoizeFunction,
ArgsMemoizeFunction,
OverrideStateType
Expand Down
166 changes: 96 additions & 70 deletions src/createStructuredSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,40 +41,9 @@ export type RootStateSelectors<RootState = any> = {
}

/**
* 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<RootState> =
* 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<RootState>()} 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
*/
Expand Down Expand Up @@ -205,22 +174,27 @@ export type TypedStructuredSelectorCreator<RootState = any> =
/**
* 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<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 type StructuredSelectorCreator =
export interface StructuredSelectorCreator<StateType = any> {
/**
* A convenience function that simplifies returning an object
* made up of selector results.
Expand Down Expand Up @@ -327,7 +301,7 @@ export type StructuredSelectorCreator =
* @see {@link https://reselect.js.org/api/createStructuredSelector `createStructuredSelector`}
*/
<
InputSelectorsObject extends SelectorsObject,
InputSelectorsObject extends SelectorsObject<StateType>,
MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
>(
Expand All @@ -336,18 +310,64 @@ export type StructuredSelectorCreator =
MemoizeFunction,
ArgsMemoizeFunction
>
) => OutputSelector<
): 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.0.2
*/
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
Expand Down Expand Up @@ -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
27 changes: 27 additions & 0 deletions test/createStructuredSelector.withTypes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createStructuredSelector } from 'reselect'
import type { RootState } from './testUtils'
import { localTest } from './testUtils'

describe(createStructuredSelector.withTypes, () => {
const createTypedStructuredSelector =
createStructuredSelector.withTypes<RootState>()

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
})
})
3 changes: 3 additions & 0 deletions type-tests/createStructuredSelector.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RootState> =
createStructuredSelector
Expand Down Expand Up @@ -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,
Expand Down
Loading
Loading