Skip to content

Commit a39fe9e

Browse files
authored
Merge pull request #678 from aryaemami59/createStructuredSelector-withTypes
Introduce pre-typed `createStructuredSelector` via `createStructuredSelector.ts.withTypes<RootState>()` method
2 parents 7bd3d6c + 1368970 commit a39fe9e

14 files changed

+720
-147
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ Version 5.0.0 introduces several new features and improvements:
149149
- **Selector API Enhancements**:
150150

151151
- Removed the second overload of `createStructuredSelector` due to its susceptibility to runtime errors.
152-
- 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.
153152

154153
- **Additional Functionalities**:
155154

docs/examples/createStructuredSelector/createStructuredAppSelector.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { createStructuredSelector } from 'reselect'
2+
3+
export interface RootState {
4+
todos: { id: number; completed: boolean }[]
5+
alerts: { id: number; read: boolean }[]
6+
}
7+
8+
export const createStructuredAppSelector =
9+
createStructuredSelector.withTypes<RootState>()
10+
11+
const structuredAppSelector = createStructuredAppSelector({
12+
// Type of `state` is set to `RootState`, no need to manually set the type
13+
// highlight-start
14+
todos: state => state.todos,
15+
// highlight-end
16+
alerts: state => state.alerts,
17+
todoById: (state, id: number) => state.todos[id]
18+
})

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "reselect",
3-
"version": "5.0.1",
3+
"version": "5.0.2",
44
"description": "Selectors for Redux.",
55
"main": "./dist/cjs/reselect.cjs",
66
"module": "./dist/reselect.legacy-esm.js",

src/createSelectorCreator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export interface CreateSelectorFunction<
176176
*
177177
* @since 5.0.2
178178
*/
179-
withTypes<OverrideStateType extends StateType>(): CreateSelectorFunction<
179+
withTypes: <OverrideStateType extends StateType>() => CreateSelectorFunction<
180180
MemoizeFunction,
181181
ArgsMemoizeFunction,
182182
OverrideStateType

src/createStructuredSelector.ts

Lines changed: 96 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -41,40 +41,9 @@ export type RootStateSelectors<RootState = any> = {
4141
}
4242

4343
/**
44-
* Allows you to create a pre-typed version of
45-
* {@linkcode createStructuredSelector createStructuredSelector}
46-
* tailored to the provided root state type.
47-
*
48-
* @example
49-
* ```ts
50-
* import type { TypedStructuredSelectorCreator } from 'reselect'
51-
* import { createStructuredSelector } from 'reselect'
52-
*
53-
* interface RootState {
54-
* todos: {
55-
* id: number
56-
* completed: boolean
57-
* title: string
58-
* description: string
59-
* }[]
60-
* alerts: { id: number; read: boolean }[]
61-
* }
62-
*
63-
* export const createStructuredAppSelector: TypedStructuredSelectorCreator<RootState> =
64-
* createStructuredSelector
65-
*
66-
* const structuredSelector = createStructuredAppSelector({
67-
* // The `state` argument is correctly typed as `RootState`
68-
* todos: state => state.todos,
69-
* alerts: state => state.alerts
70-
* })
71-
*
72-
* ```
73-
*
44+
* @deprecated Please use {@linkcode StructuredSelectorCreator.withTypes createStructuredSelector.withTypes<RootState>()} instead. This type will be removed in the future.
7445
* @template RootState - The type of the root state object.
7546
*
76-
* @see {@link https://reselect.js.org/api/createStructuredSelector#typedstructuredselectorcreator-since-500 `TypedStructuredSelectorCreator`}
77-
*
7847
* @since 5.0.0
7948
* @public
8049
*/
@@ -205,22 +174,27 @@ export type TypedStructuredSelectorCreator<RootState = any> =
205174
/**
206175
* Represents an object where each property is a selector function.
207176
*
177+
* @template StateType - The type of state that all the selectors operate on.
178+
*
208179
* @public
209180
*/
210-
export interface SelectorsObject {
211-
[key: string]: Selector
212-
}
181+
export type SelectorsObject<StateType = any> = Record<
182+
string,
183+
Selector<StateType>
184+
>
213185

214186
/**
215187
* It provides a way to create structured selectors.
216188
* The structured selector can take multiple input selectors
217189
* and map their output to an object with specific keys.
218190
*
191+
* @template StateType - The type of state that the structured selectors created with this structured selector creator will operate on.
192+
*
219193
* @see {@link https://reselect.js.org/api/createStructuredSelector `createStructuredSelector`}
220194
*
221195
* @public
222196
*/
223-
export type StructuredSelectorCreator =
197+
export interface StructuredSelectorCreator<StateType = any> {
224198
/**
225199
* A convenience function that simplifies returning an object
226200
* made up of selector results.
@@ -327,7 +301,7 @@ export type StructuredSelectorCreator =
327301
* @see {@link https://reselect.js.org/api/createStructuredSelector `createStructuredSelector`}
328302
*/
329303
<
330-
InputSelectorsObject extends SelectorsObject,
304+
InputSelectorsObject extends SelectorsObject<StateType>,
331305
MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
332306
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
333307
>(
@@ -336,18 +310,64 @@ export type StructuredSelectorCreator =
336310
MemoizeFunction,
337311
ArgsMemoizeFunction
338312
>
339-
) => OutputSelector<
313+
): OutputSelector<
340314
ObjectValuesToTuple<InputSelectorsObject>,
341315
Simplify<SelectorResultsMap<InputSelectorsObject>>,
342316
MemoizeFunction,
343317
ArgsMemoizeFunction
344318
> &
345319
InterruptRecursion
346320

321+
/**
322+
* Creates a "pre-typed" version of
323+
* {@linkcode createStructuredSelector createStructuredSelector}
324+
* where the `state` type is predefined.
325+
*
326+
* This allows you to set the `state` type once, eliminating the need to
327+
* specify it with every
328+
* {@linkcode createStructuredSelector createStructuredSelector} call.
329+
*
330+
* @returns A pre-typed `createStructuredSelector` with the state type already defined.
331+
*
332+
* @example
333+
* ```ts
334+
* import { createStructuredSelector } from 'reselect'
335+
*
336+
* export interface RootState {
337+
* todos: { id: number; completed: boolean }[]
338+
* alerts: { id: number; read: boolean }[]
339+
* }
340+
*
341+
* export const createStructuredAppSelector =
342+
* createStructuredSelector.withTypes<RootState>()
343+
*
344+
* const structuredAppSelector = createStructuredAppSelector({
345+
* // Type of `state` is set to `RootState`, no need to manually set the type
346+
* todos: state => state.todos,
347+
* alerts: state => state.alerts,
348+
* todoById: (state, id: number) => state.todos[id]
349+
* })
350+
*
351+
* ```
352+
* @template OverrideStateType - The specific type of state used by all structured selectors created with this structured selector creator.
353+
*
354+
* @see {@link https://reselect.js.org/api/createstructuredselector#defining-a-pre-typed-createstructuredselector `createSelector.withTypes`}
355+
*
356+
* @since 5.0.2
357+
*/
358+
withTypes: <
359+
OverrideStateType extends StateType
360+
>() => StructuredSelectorCreator<OverrideStateType>
361+
}
362+
347363
/**
348364
* A convenience function that simplifies returning an object
349365
* made up of selector results.
350366
*
367+
* @param inputSelectorsObject - A key value pair consisting of input selectors.
368+
* @param selectorCreator - A custom selector creator function. It defaults to `createSelector`.
369+
* @returns A memoized structured selector.
370+
*
351371
* @example
352372
* <caption>Modern Use Case</caption>
353373
* ```ts
@@ -394,35 +414,41 @@ export type StructuredSelectorCreator =
394414
*
395415
* @public
396416
*/
397-
export const createStructuredSelector: StructuredSelectorCreator = (<
398-
InputSelectorsObject extends SelectorsObject,
399-
MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
400-
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
401-
>(
402-
inputSelectorsObject: InputSelectorsObject,
403-
selectorCreator: CreateSelectorFunction<
404-
MemoizeFunction,
405-
ArgsMemoizeFunction
406-
> = createSelector as CreateSelectorFunction<
407-
MemoizeFunction,
408-
ArgsMemoizeFunction
409-
>
410-
) => {
411-
assertIsObject(
412-
inputSelectorsObject,
413-
'createStructuredSelector expects first argument to be an object ' +
414-
`where each property is a selector, instead received a ${typeof inputSelectorsObject}`
415-
)
416-
const inputSelectorKeys = Object.keys(inputSelectorsObject)
417-
const dependencies = inputSelectorKeys.map(key => inputSelectorsObject[key])
418-
const structuredSelector = selectorCreator(
419-
dependencies,
420-
(...inputSelectorResults: any[]) => {
421-
return inputSelectorResults.reduce((composition, value, index) => {
422-
composition[inputSelectorKeys[index]] = value
423-
return composition
424-
}, {})
425-
}
426-
)
427-
return structuredSelector
428-
}) as StructuredSelectorCreator
417+
export const createStructuredSelector: StructuredSelectorCreator =
418+
Object.assign(
419+
<
420+
InputSelectorsObject extends SelectorsObject,
421+
MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
422+
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
423+
>(
424+
inputSelectorsObject: InputSelectorsObject,
425+
selectorCreator: CreateSelectorFunction<
426+
MemoizeFunction,
427+
ArgsMemoizeFunction
428+
> = createSelector as CreateSelectorFunction<
429+
MemoizeFunction,
430+
ArgsMemoizeFunction
431+
>
432+
) => {
433+
assertIsObject(
434+
inputSelectorsObject,
435+
'createStructuredSelector expects first argument to be an object ' +
436+
`where each property is a selector, instead received a ${typeof inputSelectorsObject}`
437+
)
438+
const inputSelectorKeys = Object.keys(inputSelectorsObject)
439+
const dependencies = inputSelectorKeys.map(
440+
key => inputSelectorsObject[key]
441+
)
442+
const structuredSelector = selectorCreator(
443+
dependencies,
444+
(...inputSelectorResults: any[]) => {
445+
return inputSelectorResults.reduce((composition, value, index) => {
446+
composition[inputSelectorKeys[index]] = value
447+
return composition
448+
}, {})
449+
}
450+
)
451+
return structuredSelector
452+
},
453+
{ withTypes: () => createStructuredSelector }
454+
) as StructuredSelectorCreator
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { createStructuredSelector } from 'reselect'
2+
import type { RootState } from './testUtils'
3+
import { localTest } from './testUtils'
4+
5+
describe(createStructuredSelector.withTypes, () => {
6+
const createTypedStructuredSelector =
7+
createStructuredSelector.withTypes<RootState>()
8+
9+
localTest('should return createStructuredSelector', ({ state }) => {
10+
expect(createTypedStructuredSelector.withTypes).to.be.a('function')
11+
12+
expect(createTypedStructuredSelector.withTypes().withTypes).to.be.a(
13+
'function'
14+
)
15+
16+
expect(createTypedStructuredSelector).toBe(createStructuredSelector)
17+
18+
const structuredSelector = createTypedStructuredSelector({
19+
todos: state => state.todos,
20+
alerts: state => state.alerts
21+
})
22+
23+
expect(structuredSelector).toBeMemoizedSelector()
24+
25+
expect(structuredSelector(state)).to.be.an('object').that.is.not.empty
26+
})
27+
})

type-tests/createStructuredSelector.test-d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const rootState: RootState = {
3535
}
3636

3737
describe('createStructuredSelector', () => {
38+
39+
// TODO: Remove this test block once `TypedStructuredSelectorCreator` is removed.
3840
test('TypedStructuredSelectorCreator should lock down state type', () => {
3941
const createStructuredAppSelector: TypedStructuredSelectorCreator<RootState> =
4042
createStructuredSelector
@@ -112,6 +114,7 @@ describe('createStructuredSelector', () => {
112114
>(structuredSelector.lastResult())
113115
})
114116

117+
// TODO: Remove this test block once `TypedStructuredSelectorCreator` is removed.
115118
test('TypedStructuredSelectorCreator should correctly infer memoize and argsMemoize', () => {
116119
const createSelectorLru = createSelectorCreator({
117120
memoize: lruMemoize,

0 commit comments

Comments
 (0)