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

Lazy react hook module concept #4128

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
26 changes: 18 additions & 8 deletions packages/toolkit/src/query/apiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ export interface ApiModules<

export type ModuleName = keyof ApiModules<any, any, any, any>

export type DefaultedOptions =
| 'reducerPath'
| 'serializeQueryArgs'
| 'keepUnusedDataFor'
| 'refetchOnMountOrArgChange'
| 'refetchOnFocus'
| 'refetchOnReconnect'
| 'invalidationBehavior'
| 'tagTypes'

export type Module<Name extends ModuleName> = {
name: Name
init<
Expand All @@ -39,14 +49,7 @@ export type Module<Name extends ModuleName> = {
api: Api<BaseQuery, EndpointDefinitions, ReducerPath, TagTypes, ModuleName>,
options: WithRequiredProp<
CreateApiOptions<BaseQuery, Definitions, ReducerPath, TagTypes>,
| 'reducerPath'
| 'serializeQueryArgs'
| 'keepUnusedDataFor'
| 'refetchOnMountOrArgChange'
| 'refetchOnFocus'
| 'refetchOnReconnect'
| 'invalidationBehavior'
| 'tagTypes'
DefaultedOptions
>,
context: ApiContext<Definitions>,
): {
Expand Down Expand Up @@ -117,4 +120,11 @@ export type Api<
TagTypes | NewTagTypes,
Enhancers
>
internal: {
options: WithRequiredProp<
CreateApiOptions<any, any, any, any>,
DefaultedOptions
>
endpoints: Definitions
}
}
21 changes: 17 additions & 4 deletions packages/toolkit/src/query/createApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import type { Api, ApiContext, Module, ModuleName } from './apiTypes'
import type {
Api,
ApiContext,
DefaultedOptions,
Module,
ModuleName,
} from './apiTypes'
import type { CombinedState } from './core/apiState'
import type { BaseQueryArg, BaseQueryFn } from './baseQueryTypes'
import type { SerializeQueryArgs } from './defaultSerializeQueryArgs'
Expand All @@ -10,7 +16,7 @@ import type {
import { DefinitionType, isQueryDefinition } from './endpointDefinitions'
import { nanoid } from './core/rtkImports'
import type { UnknownAction } from '@reduxjs/toolkit'
import type { NoInfer } from './tsHelpers'
import type { NoInfer, WithRequiredProp } from './tsHelpers'
import { weakMapMemoize } from 'reselect'

export interface CreateApiOptions<
Expand Down Expand Up @@ -259,7 +265,10 @@ export function buildCreateApi<Modules extends [Module<any>, ...Module<any>[]]>(
}),
)

const optionsWithDefaults: CreateApiOptions<any, any, any, any> = {
const optionsWithDefaults: WithRequiredProp<
CreateApiOptions<any, any, any, any>,
DefaultedOptions
> = {
reducerPath: 'api',
keepUnusedDataFor: 60,
refetchOnMountOrArgChange: false,
Expand Down Expand Up @@ -335,10 +344,14 @@ export function buildCreateApi<Modules extends [Module<any>, ...Module<any>[]]>(
}
return api
},
internal: {
options: optionsWithDefaults,
endpoints: context.endpointDefinitions,
},
} as Api<BaseQueryFn, {}, string, string, Modules[number]['name']>

const initializedModules = modules.map((m) =>
m.init(api as any, optionsWithDefaults as any, context),
m.init(api as any, optionsWithDefaults, context),
)

function injectEndpoints(
Expand Down
10 changes: 5 additions & 5 deletions packages/toolkit/src/query/react/buildHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,12 +579,12 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
createSelector,
},
serializeQueryArgs,
context,
endpointDefinitions,
}: {
api: Api<any, Definitions, any, any, CoreModule>
moduleOptions: Required<ReactHooksModuleOptions>
serializeQueryArgs: SerializeQueryArgs<any>
context: ApiContext<Definitions>
endpointDefinitions: Definitions
}) {
const usePossiblyImmediateEffect: (
effect: () => void | undefined,
Expand All @@ -603,7 +603,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
// in this case, reset the hook
if (lastResult?.endpointName && currentState.isUninitialized) {
const { endpointName } = lastResult
const endpointDefinition = context.endpointDefinitions[endpointName]
const endpointDefinition = endpointDefinitions[endpointName]
if (
serializeQueryArgs({
queryArgs: lastResult.originalArgs,
Expand Down Expand Up @@ -707,7 +707,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
// with a case where the query args did change but the serialization doesn't,
// and then we never try to initiate a refetch.
defaultSerializeQueryArgs,
context.endpointDefinitions[name],
endpointDefinitions[name],
name,
)
const stableSubscriptionOptions = useShallowStableValue({
Expand Down Expand Up @@ -898,7 +898,7 @@ export function buildHooks<Definitions extends EndpointDefinitions>({
const stableArg = useStableQueryArgs(
skip ? skipToken : arg,
serializeQueryArgs,
context.endpointDefinitions[name],
endpointDefinitions[name],
name,
)

Expand Down
8 changes: 6 additions & 2 deletions packages/toolkit/src/query/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import { formatProdErrorMessage } from '@reduxjs/toolkit'

import { buildCreateApi, coreModule } from '@reduxjs/toolkit/query'
import { reactHooksModule, reactHooksModuleName } from './module'
import {
reactHooksModule,
reactHooksModuleName,
buildHooksForApi,
} from './module'

export * from '@reduxjs/toolkit/query'
export { ApiProvider } from './ApiProvider'
Expand All @@ -19,4 +23,4 @@ export type {
TypedUseQueryStateResult,
TypedUseQuerySubscriptionResult,
} from './buildHooks'
export { createApi, reactHooksModule, reactHooksModuleName }
export { createApi, reactHooksModule, reactHooksModuleName, buildHooksForApi }
162 changes: 122 additions & 40 deletions packages/toolkit/src/query/react/module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
Api,
ApiModules,
BaseQueryFn,
EndpointDefinitions,
Module,
Expand Down Expand Up @@ -119,6 +120,58 @@ export interface ReactHooksModuleOptions {
createSelector?: typeof _createSelector
}

function buildInjectEndpoint(
target: Omit<
ApiModules<any, Record<string, any>, any, any>[ReactHooksModule],
'usePrefetch'
>,
{
buildMutationHook,
buildQueryHooks,
}: Pick<
ReturnType<typeof buildHooks>,
'buildQueryHooks' | 'buildMutationHook'
>,
): ReturnType<Module<ReactHooksModule>['init']>['injectEndpoint'] {
return function injectEndpoint(endpointName, definition) {
if (isQueryDefinition(definition)) {
const {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription,
} = buildQueryHooks(endpointName)
safeAssign(target.endpoints[endpointName], {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription,
})
;(target as any)[`use${capitalize(endpointName)}Query`] = useQuery
;(target as any)[`useLazy${capitalize(endpointName)}Query`] = useLazyQuery
} else if (isMutationDefinition(definition)) {
const useMutation = buildMutationHook(endpointName)
safeAssign(target.endpoints[endpointName], {
useMutation,
})
;(target as any)[`use${capitalize(endpointName)}Mutation`] = useMutation
}
}
}

const defaultOptions: Required<ReactHooksModuleOptions> = {
batch: rrBatch,
hooks: {
useDispatch: rrUseDispatch,
useSelector: rrUseSelector,
useStore: rrUseStore,
},
createSelector: _createSelector,
unstable__sideEffectsInRender: false,
}

/**
* Creates a module that generates react hooks from endpoints, for use with `buildCreateApi`.
*
Expand All @@ -139,17 +192,16 @@ export interface ReactHooksModuleOptions {
*
* @returns A module for use with `buildCreateApi`
*/
export const reactHooksModule = ({
batch = rrBatch,
hooks = {
useDispatch: rrUseDispatch,
useSelector: rrUseSelector,
useStore: rrUseStore,
},
createSelector = _createSelector,
unstable__sideEffectsInRender = false,
...rest
}: ReactHooksModuleOptions = {}): Module<ReactHooksModule> => {
export const reactHooksModule = (
moduleOptions?: ReactHooksModuleOptions,
): Module<ReactHooksModule> => {
const {
batch,
hooks,
createSelector,
unstable__sideEffectsInRender,
...rest
} = { ...defaultOptions, ...moduleOptions }
if (process.env.NODE_ENV !== 'production') {
const hookNames = ['useDispatch', 'useSelector', 'useStore'] as const
let warned = false
Expand Down Expand Up @@ -201,41 +253,71 @@ export const reactHooksModule = ({
createSelector,
},
serializeQueryArgs,
context,
endpointDefinitions: context.endpointDefinitions,
})

safeAssign(anyApi, { usePrefetch })
safeAssign(context, { batch })

return {
injectEndpoint(endpointName, definition) {
if (isQueryDefinition(definition)) {
const {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription,
} = buildQueryHooks(endpointName)
safeAssign(anyApi.endpoints[endpointName], {
useQuery,
useLazyQuery,
useLazyQuerySubscription,
useQueryState,
useQuerySubscription,
})
;(api as any)[`use${capitalize(endpointName)}Query`] = useQuery
;(api as any)[`useLazy${capitalize(endpointName)}Query`] =
useLazyQuery
} else if (isMutationDefinition(definition)) {
const useMutation = buildMutationHook(endpointName)
safeAssign(anyApi.endpoints[endpointName], {
useMutation,
})
;(api as any)[`use${capitalize(endpointName)}Mutation`] =
useMutation
}
},
injectEndpoint: buildInjectEndpoint(anyApi, {
buildMutationHook,
buildQueryHooks,
}),
}
},
}
}

export const buildHooksForApi = <
BaseQuery extends BaseQueryFn,
Definitions extends EndpointDefinitions,
ReducerPath extends string,
TagTypes extends string,
>(
api: Api<BaseQuery, Definitions, ReducerPath, TagTypes>,
options?: ReactHooksModuleOptions,
): ApiModules<
BaseQuery,
Definitions,
ReducerPath,
TagTypes
>[ReactHooksModule] => {
const { batch, hooks, unstable__sideEffectsInRender, createSelector } = {
...defaultOptions,
...options,
}

const { buildQueryHooks, buildMutationHook, usePrefetch } = buildHooks({
api,
moduleOptions: {
batch,
hooks,
unstable__sideEffectsInRender,
createSelector,
},
serializeQueryArgs: api.internal.options.serializeQueryArgs,
endpointDefinitions: api.internal.endpoints,
})

const result: {
endpoints: Record<string, QueryHooks<any> | MutationHooks<any>>
usePrefetch: typeof usePrefetch
} = {
endpoints: {},
usePrefetch,
}

const injectEndpoint = buildInjectEndpoint(result, {
buildMutationHook,
buildQueryHooks,
})

for (const [endpointName, definition] of Object.entries(
api.internal.endpoints,
)) {
result.endpoints[endpointName] = {} as any
injectEndpoint(endpointName, definition)
}
return result as any
}
Loading
Loading