Skip to content
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