UNPKG

7.17 kBPlain TextView Raw
1import type {
2 Api,
3 BaseQueryFn,
4 EndpointDefinitions,
5 Module,
6 MutationDefinition,
7 QueryArgFrom,
8 QueryDefinition,
9} from '@reduxjs/toolkit/query'
10import { isMutationDefinition, isQueryDefinition } from '../endpointDefinitions'
11import { safeAssign } from '../tsHelpers'
12import { capitalize } from '../utils'
13import type { MutationHooks, QueryHooks } from './buildHooks'
14import { buildHooks } from './buildHooks'
15
16import type { HooksWithUniqueNames } from './namedHooks'
17
18import {
19 batch as rrBatch,
20 useDispatch as rrUseDispatch,
21 useSelector as rrUseSelector,
22 useStore as rrUseStore,
23} from 'react-redux'
24import { createSelector as _createSelector } from 'reselect'
25import type { QueryKeys } from '../core/apiState'
26import type { PrefetchOptions } from '../core/module'
27import { countObjectKeys } from '../utils/countObjectKeys'
28
29export const reactHooksModuleName = /* @__PURE__ */ Symbol()
30export type ReactHooksModule = typeof reactHooksModuleName
31
32declare module '@reduxjs/toolkit/query' {
33 export interface ApiModules<
34 // eslint-disable-next-line @typescript-eslint/no-unused-vars
35 BaseQuery extends BaseQueryFn,
36 Definitions extends EndpointDefinitions,
37 // eslint-disable-next-line @typescript-eslint/no-unused-vars
38 ReducerPath extends string,
39 // eslint-disable-next-line @typescript-eslint/no-unused-vars
40 TagTypes extends string,
41 > {
42 [reactHooksModuleName]: {
43 /**
44 * Endpoints based on the input endpoints provided to `createApi`, containing `select`, `hooks` and `action matchers`.
45 */
46 endpoints: {
47 [K in keyof Definitions]: Definitions[K] extends QueryDefinition<
48 any,
49 any,
50 any,
51 any,
52 any
53 >
54 ? QueryHooks<Definitions[K]>
55 : Definitions[K] extends MutationDefinition<any, any, any, any, any>
56 ? MutationHooks<Definitions[K]>
57 : never
58 }
59 /**
60 * A hook that accepts a string endpoint name, and provides a callback that when called, pre-fetches the data for that endpoint.
61 */
62 usePrefetch<EndpointName extends QueryKeys<Definitions>>(
63 endpointName: EndpointName,
64 options?: PrefetchOptions,
65 ): (
66 arg: QueryArgFrom<Definitions[EndpointName]>,
67 options?: PrefetchOptions,
68 ) => void
69 } & HooksWithUniqueNames<Definitions>
70 }
71}
72
73type RR = typeof import('react-redux')
74
75export interface ReactHooksModuleOptions {
76 /**
77 * The hooks from React Redux to be used
78 */
79 hooks?: {
80 /**
81 * The version of the `useDispatch` hook to be used
82 */
83 useDispatch: RR['useDispatch']
84 /**
85 * The version of the `useSelector` hook to be used
86 */
87 useSelector: RR['useSelector']
88 /**
89 * The version of the `useStore` hook to be used
90 */
91 useStore: RR['useStore']
92 }
93 /**
94 * The version of the `batchedUpdates` function to be used
95 */
96 batch?: RR['batch']
97 /**
98 * Enables performing asynchronous tasks immediately within a render.
99 *
100 * @example
101 *
102 * ```ts
103 * import {
104 * buildCreateApi,
105 * coreModule,
106 * reactHooksModule
107 * } from '@reduxjs/toolkit/query/react'
108 *
109 * const createApi = buildCreateApi(
110 * coreModule(),
111 * reactHooksModule({ unstable__sideEffectsInRender: true })
112 * )
113 * ```
114 */
115 unstable__sideEffectsInRender?: boolean
116 /**
117 * A selector creator (usually from `reselect`, or matching the same signature)
118 */
119 createSelector?: typeof _createSelector
120}
121
122/**
123 * Creates a module that generates react hooks from endpoints, for use with `buildCreateApi`.
124 *
125 * @example
126 * ```ts
127 * const MyContext = React.createContext<ReactReduxContextValue | null>(null);
128 * const customCreateApi = buildCreateApi(
129 * coreModule(),
130 * reactHooksModule({
131 * hooks: {
132 * useDispatch: createDispatchHook(MyContext),
133 * useSelector: createSelectorHook(MyContext),
134 * useStore: createStoreHook(MyContext)
135 * }
136 * })
137 * );
138 * ```
139 *
140 * @returns A module for use with `buildCreateApi`
141 */
142export const reactHooksModule = ({
143 batch = rrBatch,
144 hooks = {
145 useDispatch: rrUseDispatch,
146 useSelector: rrUseSelector,
147 useStore: rrUseStore,
148 },
149 createSelector = _createSelector,
150 unstable__sideEffectsInRender = false,
151 ...rest
152}: ReactHooksModuleOptions = {}): Module<ReactHooksModule> => {
153 if (process.env.NODE_ENV !== 'production') {
154 const hookNames = ['useDispatch', 'useSelector', 'useStore'] as const
155 let warned = false
156 for (const hookName of hookNames) {
157 // warn for old hook options
158 if (countObjectKeys(rest) > 0) {
159 if ((rest as Partial<typeof hooks>)[hookName]) {
160 if (!warned) {
161 console.warn(
162 'As of RTK 2.0, the hooks now need to be specified as one object, provided under a `hooks` key:' +
163 '\n`reactHooksModule({ hooks: { useDispatch, useSelector, useStore } })`',
164 )
165 warned = true
166 }
167 }
168 // migrate
169 // @ts-ignore
170 hooks[hookName] = rest[hookName]
171 }
172 // then make sure we have them all
173 if (typeof hooks[hookName] !== 'function') {
174 throw new Error(
175 `When using custom hooks for context, all ${
176 hookNames.length
177 } hooks need to be provided: ${hookNames.join(
178 ', ',
179 )}.\nHook ${hookName} was either not provided or not a function.`,
180 )
181 }
182 }
183 }
184
185 return {
186 name: reactHooksModuleName,
187 init(api, { serializeQueryArgs }, context) {
188 const anyApi = api as any as Api<
189 any,
190 Record<string, any>,
191 any,
192 any,
193 ReactHooksModule
194 >
195 const { buildQueryHooks, buildMutationHook, usePrefetch } = buildHooks({
196 api,
197 moduleOptions: {
198 batch,
199 hooks,
200 unstable__sideEffectsInRender,
201 createSelector,
202 },
203 serializeQueryArgs,
204 context,
205 })
206 safeAssign(anyApi, { usePrefetch })
207 safeAssign(context, { batch })
208
209 return {
210 injectEndpoint(endpointName, definition) {
211 if (isQueryDefinition(definition)) {
212 const {
213 useQuery,
214 useLazyQuery,
215 useLazyQuerySubscription,
216 useQueryState,
217 useQuerySubscription,
218 } = buildQueryHooks(endpointName)
219 safeAssign(anyApi.endpoints[endpointName], {
220 useQuery,
221 useLazyQuery,
222 useLazyQuerySubscription,
223 useQueryState,
224 useQuerySubscription,
225 })
226 ;(api as any)[`use${capitalize(endpointName)}Query`] = useQuery
227 ;(api as any)[`useLazy${capitalize(endpointName)}Query`] =
228 useLazyQuery
229 } else if (isMutationDefinition(definition)) {
230 const useMutation = buildMutationHook(endpointName)
231 safeAssign(anyApi.endpoints[endpointName], {
232 useMutation,
233 })
234 ;(api as any)[`use${capitalize(endpointName)}Mutation`] =
235 useMutation
236 }
237 },
238 }
239 },
240 }
241}