Skip to content

Commit

Permalink
Merge pull request #667 from aryaemami59/TypedStructuredSelectorCreator
Browse files Browse the repository at this point in the history
Wrap up implementation of `TypedStructuredSelectorCreator`
  • Loading branch information
EskiMojo14 authored Jan 4, 2024
2 parents ab1188f + 2af0ce7 commit 7bd3d6c
Show file tree
Hide file tree
Showing 10 changed files with 556 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test-types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ jobs:
fail-fast: false
matrix:
node: ['16.x']
example: ['cra4', 'cra5', 'next', 'vite', 'node-standard', 'node-esm']
example: ['cra4', 'cra5', 'next', 'vite', 'node-standard', 'node-esm', 'expo']
steps:
- name: Checkout repo
uses: actions/checkout@v4
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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
})
233 changes: 210 additions & 23 deletions src/createStructuredSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,46 +13,201 @@ import { assertIsObject } from './utils'
import type { weakMapMemoize } from './weakMapMemoize'

/**
* Represents a mapping of selectors to their return types.
*
* @WIP
* @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
*/
type SelectorsMap<T extends SelectorsObject> = {
[Key in keyof T]: ReturnType<T[Key]>
export type RootStateSelectors<RootState = any> = {
[Key in keyof RootState]: Selector<RootState, RootState[Key], []>
}

// TODO: Write more type tests for `TypedStructuredSelectorCreator`.
/**
* Allows you to create a pre-typed version of {@linkcode createStructuredSelector createStructuredSelector}
* For your root state.
* 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
* })
*
* ```
*
* @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
* @WIP
*/
export interface TypedStructuredSelectorCreator<RootState = any> {
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 {
[Key in keyof RootState]: Selector<RootState, RootState[Key], []>
} = {
[Key in keyof RootState]: Selector<RootState, RootState[Key], []>
},
InputSelectorsObject extends RootStateSelectors<RootState> = RootStateSelectors<RootState>,
MemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize,
ArgsMemoizeFunction extends UnknownMemoizer = typeof weakMapMemoize
>(
selectors: InputSelectorsObject,
inputSelectorsObject: InputSelectorsObject,
selectorCreator?: CreateSelectorFunction<
MemoizeFunction,
ArgsMemoizeFunction
>
): OutputSelector<
) => OutputSelector<
ObjectValuesToTuple<InputSelectorsObject>,
SelectorsMap<InputSelectorsObject>,
Simplify<SelectorResultsMap<InputSelectorsObject>>,
MemoizeFunction,
ArgsMemoizeFunction
>
}
> &
InterruptRecursion

interface SelectorsObject {
/**
* Represents an object where each property is a selector function.
*
* @public
*/
export interface SelectorsObject {
[key: string]: Selector
}

Expand All @@ -65,7 +220,7 @@ interface SelectorsObject {
*
* @public
*/
export interface StructuredSelectorCreator {
export type StructuredSelectorCreator =
/**
* A convenience function that simplifies returning an object
* made up of selector results.
Expand Down Expand Up @@ -117,6 +272,39 @@ export interface StructuredSelectorCreator {
* ```
*
* @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
Expand Down Expand Up @@ -148,14 +336,13 @@ export interface StructuredSelectorCreator {
MemoizeFunction,
ArgsMemoizeFunction
>
): OutputSelector<
) => OutputSelector<
ObjectValuesToTuple<InputSelectorsObject>,
Simplify<SelectorsMap<InputSelectorsObject>>,
Simplify<SelectorResultsMap<InputSelectorsObject>>,
MemoizeFunction,
ArgsMemoizeFunction
> &
InterruptRecursion
}

/**
* A convenience function that simplifies returning an object
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ export { createSelector, createSelectorCreator } from './createSelectorCreator'
export type { CreateSelectorFunction } from './createSelectorCreator'
export { createStructuredSelector } from './createStructuredSelector'
export type {
RootStateSelectors,
SelectorResultsMap,
SelectorsObject,
StructuredSelectorCreator,
TypedStructuredSelectorCreator
} from './createStructuredSelector'
Expand Down
2 changes: 1 addition & 1 deletion test/createStructuredSelector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface StateAB {
b: number
}

describe('createStructureSelector', () => {
describe(createStructuredSelector, () => {
test('structured selector', () => {
const selector = createStructuredSelector({
x: (state: StateAB) => state.a,
Expand Down
Loading

0 comments on commit 7bd3d6c

Please sign in to comment.