1 | import type {
|
2 | Selector,
|
3 | ThunkAction,
|
4 | ThunkDispatch,
|
5 | UnknownAction,
|
6 | } from '@reduxjs/toolkit'
|
7 | import type {
|
8 | Api,
|
9 | ApiContext,
|
10 | ApiEndpointMutation,
|
11 | ApiEndpointQuery,
|
12 | CoreModule,
|
13 | EndpointDefinitions,
|
14 | MutationActionCreatorResult,
|
15 | MutationDefinition,
|
16 | MutationResultSelectorResult,
|
17 | PrefetchOptions,
|
18 | QueryActionCreatorResult,
|
19 | QueryArgFrom,
|
20 | QueryDefinition,
|
21 | QueryKeys,
|
22 | QueryResultSelectorResult,
|
23 | QuerySubState,
|
24 | ResultTypeFrom,
|
25 | RootState,
|
26 | SerializeQueryArgs,
|
27 | SkipToken,
|
28 | SubscriptionOptions,
|
29 | TSHelpersId,
|
30 | TSHelpersNoInfer,
|
31 | TSHelpersOverride,
|
32 | } from '@reduxjs/toolkit/query'
|
33 | import { QueryStatus, skipToken } from '@reduxjs/toolkit/query'
|
34 | import type { DependencyList } from 'react'
|
35 | import {
|
36 | useCallback,
|
37 | useDebugValue,
|
38 | useEffect,
|
39 | useLayoutEffect,
|
40 | useMemo,
|
41 | useRef,
|
42 | useState,
|
43 | } from 'react'
|
44 |
|
45 | import { shallowEqual } from 'react-redux'
|
46 | import type { BaseQueryFn } from '../baseQueryTypes'
|
47 | import type { SubscriptionSelectors } from '../core/buildMiddleware/types'
|
48 | import { defaultSerializeQueryArgs } from '../defaultSerializeQueryArgs'
|
49 | import type { UninitializedValue } from './constants'
|
50 | import { UNINITIALIZED_VALUE } from './constants'
|
51 | import type { ReactHooksModuleOptions } from './module'
|
52 | import { useStableQueryArgs } from './useSerializedStableValue'
|
53 | import { useShallowStableValue } from './useShallowStableValue'
|
54 |
|
55 |
|
56 | export const useIsomorphicLayoutEffect =
|
57 | typeof window !== 'undefined' &&
|
58 | !!window.document &&
|
59 | !!window.document.createElement
|
60 | ? useLayoutEffect
|
61 | : useEffect
|
62 |
|
63 | export interface QueryHooks<
|
64 | Definition extends QueryDefinition<any, any, any, any, any>,
|
65 | > {
|
66 | useQuery: UseQuery<Definition>
|
67 | useLazyQuery: UseLazyQuery<Definition>
|
68 | useQuerySubscription: UseQuerySubscription<Definition>
|
69 | useLazyQuerySubscription: UseLazyQuerySubscription<Definition>
|
70 | useQueryState: UseQueryState<Definition>
|
71 | }
|
72 |
|
73 | export interface MutationHooks<
|
74 | Definition extends MutationDefinition<any, any, any, any, any>,
|
75 | > {
|
76 | useMutation: UseMutation<Definition>
|
77 | }
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | export type UseQuery<D extends QueryDefinition<any, any, any, any>> = <
|
95 | R extends Record<string, any> = UseQueryStateDefaultResult<D>,
|
96 | >(
|
97 | arg: QueryArgFrom<D> | SkipToken,
|
98 | options?: UseQuerySubscriptionOptions & UseQueryStateOptions<D, R>,
|
99 | ) => UseQueryHookResult<D, R>
|
100 |
|
101 | export type TypedUseQuery<
|
102 | ResultType,
|
103 | QueryArg,
|
104 | BaseQuery extends BaseQueryFn,
|
105 | > = UseQuery<QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>>
|
106 |
|
107 | export type UseQueryHookResult<
|
108 | D extends QueryDefinition<any, any, any, any>,
|
109 | R = UseQueryStateDefaultResult<D>,
|
110 | > = UseQueryStateResult<D, R> & UseQuerySubscriptionResult<D>
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 | export type TypedUseQueryHookResult<
|
117 | ResultType,
|
118 | QueryArg,
|
119 | BaseQuery extends BaseQueryFn,
|
120 | R = UseQueryStateDefaultResult<
|
121 | QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
122 | >,
|
123 | > = TypedUseQueryStateResult<ResultType, QueryArg, BaseQuery, R> &
|
124 | TypedUseQuerySubscriptionResult<ResultType, QueryArg, BaseQuery>
|
125 |
|
126 | interface UseQuerySubscriptionOptions extends SubscriptionOptions {
|
127 | |
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 | skip?: boolean
|
160 | |
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | refetchOnMountOrArgChange?: boolean | number
|
169 | }
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 | export type UseQuerySubscription<
|
185 | D extends QueryDefinition<any, any, any, any>,
|
186 | > = (
|
187 | arg: QueryArgFrom<D> | SkipToken,
|
188 | options?: UseQuerySubscriptionOptions,
|
189 | ) => UseQuerySubscriptionResult<D>
|
190 |
|
191 | export type TypedUseQuerySubscription<
|
192 | ResultType,
|
193 | QueryArg,
|
194 | BaseQuery extends BaseQueryFn,
|
195 | > = UseQuerySubscription<
|
196 | QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
197 | >
|
198 |
|
199 | export type UseQuerySubscriptionResult<
|
200 | D extends QueryDefinition<any, any, any, any>,
|
201 | > = Pick<QueryActionCreatorResult<D>, 'refetch'>
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 | export type TypedUseQuerySubscriptionResult<
|
208 | ResultType,
|
209 | QueryArg,
|
210 | BaseQuery extends BaseQueryFn,
|
211 | > = UseQuerySubscriptionResult<
|
212 | QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
213 | >
|
214 |
|
215 | export type UseLazyQueryLastPromiseInfo<
|
216 | D extends QueryDefinition<any, any, any, any>,
|
217 | > = {
|
218 | lastArg: QueryArgFrom<D>
|
219 | }
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 | export type UseLazyQuery<D extends QueryDefinition<any, any, any, any>> = <
|
239 | R extends Record<string, any> = UseQueryStateDefaultResult<D>,
|
240 | >(
|
241 | options?: SubscriptionOptions & Omit<UseQueryStateOptions<D, R>, 'skip'>,
|
242 | ) => [
|
243 | LazyQueryTrigger<D>,
|
244 | UseQueryStateResult<D, R>,
|
245 | UseLazyQueryLastPromiseInfo<D>,
|
246 | ]
|
247 |
|
248 | export type TypedUseLazyQuery<
|
249 | ResultType,
|
250 | QueryArg,
|
251 | BaseQuery extends BaseQueryFn,
|
252 | > = UseLazyQuery<
|
253 | QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
254 | >
|
255 |
|
256 | export type LazyQueryTrigger<D extends QueryDefinition<any, any, any, any>> = {
|
257 | |
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 | (
|
278 | arg: QueryArgFrom<D>,
|
279 | preferCacheValue?: boolean,
|
280 | ): QueryActionCreatorResult<D>
|
281 | }
|
282 |
|
283 | export type TypedLazyQueryTrigger<
|
284 | ResultType,
|
285 | QueryArg,
|
286 | BaseQuery extends BaseQueryFn,
|
287 | > = LazyQueryTrigger<
|
288 | QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
289 | >
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 | export type UseLazyQuerySubscription<
|
303 | D extends QueryDefinition<any, any, any, any>,
|
304 | > = (
|
305 | options?: SubscriptionOptions,
|
306 | ) => readonly [LazyQueryTrigger<D>, QueryArgFrom<D> | UninitializedValue]
|
307 |
|
308 | export type TypedUseLazyQuerySubscription<
|
309 | ResultType,
|
310 | QueryArg,
|
311 | BaseQuery extends BaseQueryFn,
|
312 | > = UseLazyQuerySubscription<
|
313 | QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
314 | >
|
315 |
|
316 | export type QueryStateSelector<
|
317 | R extends Record<string, any>,
|
318 | D extends QueryDefinition<any, any, any, any>,
|
319 | > = (state: UseQueryStateDefaultResult<D>) => R
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 | export type UseQueryState<D extends QueryDefinition<any, any, any, any>> = <
|
332 | R extends Record<string, any> = UseQueryStateDefaultResult<D>,
|
333 | >(
|
334 | arg: QueryArgFrom<D> | SkipToken,
|
335 | options?: UseQueryStateOptions<D, R>,
|
336 | ) => UseQueryStateResult<D, R>
|
337 |
|
338 | export type TypedUseQueryState<
|
339 | ResultType,
|
340 | QueryArg,
|
341 | BaseQuery extends BaseQueryFn,
|
342 | > = UseQueryState<
|
343 | QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
344 | >
|
345 |
|
346 | export type UseQueryStateOptions<
|
347 | D extends QueryDefinition<any, any, any, any>,
|
348 | R extends Record<string, any>,
|
349 | > = {
|
350 | |
351 |
|
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 | skip?: boolean
|
383 | |
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 |
|
397 |
|
398 |
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 | selectFromResult?: QueryStateSelector<R, D>
|
414 | }
|
415 |
|
416 | export type UseQueryStateResult<
|
417 | _ extends QueryDefinition<any, any, any, any>,
|
418 | R,
|
419 | > = TSHelpersNoInfer<R>
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 | export type TypedUseQueryStateResult<
|
426 | ResultType,
|
427 | QueryArg,
|
428 | BaseQuery extends BaseQueryFn,
|
429 | R = UseQueryStateDefaultResult<
|
430 | QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
431 | >,
|
432 | > = TSHelpersNoInfer<R>
|
433 |
|
434 | type UseQueryStateBaseResult<D extends QueryDefinition<any, any, any, any>> =
|
435 | QuerySubState<D> & {
|
436 | |
437 |
|
438 |
|
439 |
|
440 |
|
441 | currentData?: ResultTypeFrom<D>
|
442 | |
443 |
|
444 |
|
445 | isUninitialized: false
|
446 | |
447 |
|
448 |
|
449 | isLoading: false
|
450 | |
451 |
|
452 |
|
453 | isFetching: false
|
454 | |
455 |
|
456 |
|
457 | isSuccess: false
|
458 | |
459 |
|
460 |
|
461 | isError: false
|
462 | }
|
463 |
|
464 | type UseQueryStateDefaultResult<D extends QueryDefinition<any, any, any, any>> =
|
465 | TSHelpersId<
|
466 | | TSHelpersOverride<
|
467 | Extract<
|
468 | UseQueryStateBaseResult<D>,
|
469 | { status: QueryStatus.uninitialized }
|
470 | >,
|
471 | { isUninitialized: true }
|
472 | >
|
473 | | TSHelpersOverride<
|
474 | UseQueryStateBaseResult<D>,
|
475 | | { isLoading: true; isFetching: boolean; data: undefined }
|
476 | | ({
|
477 | isSuccess: true
|
478 | isFetching: true
|
479 | error: undefined
|
480 | } & Required<
|
481 | Pick<UseQueryStateBaseResult<D>, 'data' | 'fulfilledTimeStamp'>
|
482 | >)
|
483 | | ({
|
484 | isSuccess: true
|
485 | isFetching: false
|
486 | error: undefined
|
487 | } & Required<
|
488 | Pick<
|
489 | UseQueryStateBaseResult<D>,
|
490 | 'data' | 'fulfilledTimeStamp' | 'currentData'
|
491 | >
|
492 | >)
|
493 | | ({ isError: true } & Required<
|
494 | Pick<UseQueryStateBaseResult<D>, 'error'>
|
495 | >)
|
496 | >
|
497 | > & {
|
498 | |
499 |
|
500 |
|
501 |
|
502 |
|
503 | status: QueryStatus
|
504 | }
|
505 |
|
506 | export type MutationStateSelector<
|
507 | R extends Record<string, any>,
|
508 | D extends MutationDefinition<any, any, any, any>,
|
509 | > = (state: MutationResultSelectorResult<D>) => R
|
510 |
|
511 | export type UseMutationStateOptions<
|
512 | D extends MutationDefinition<any, any, any, any>,
|
513 | R extends Record<string, any>,
|
514 | > = {
|
515 | selectFromResult?: MutationStateSelector<R, D>
|
516 | fixedCacheKey?: string
|
517 | }
|
518 |
|
519 | export type UseMutationStateResult<
|
520 | D extends MutationDefinition<any, any, any, any>,
|
521 | R,
|
522 | > = TSHelpersNoInfer<R> & {
|
523 | originalArgs?: QueryArgFrom<D>
|
524 | |
525 |
|
526 |
|
527 |
|
528 | reset: () => void
|
529 | }
|
530 |
|
531 |
|
532 |
|
533 |
|
534 |
|
535 | export type TypedUseMutationResult<
|
536 | ResultType,
|
537 | QueryArg,
|
538 | BaseQuery extends BaseQueryFn,
|
539 | R = MutationResultSelectorResult<
|
540 | MutationDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
541 | >,
|
542 | > = UseMutationStateResult<
|
543 | MutationDefinition<QueryArg, BaseQuery, string, ResultType, string>,
|
544 | R
|
545 | >
|
546 |
|
547 |
|
548 |
|
549 |
|
550 |
|
551 |
|
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 | export type UseMutation<D extends MutationDefinition<any, any, any, any>> = <
|
558 | R extends Record<string, any> = MutationResultSelectorResult<D>,
|
559 | >(
|
560 | options?: UseMutationStateOptions<D, R>,
|
561 | ) => readonly [MutationTrigger<D>, UseMutationStateResult<D, R>]
|
562 |
|
563 | export type TypedUseMutation<
|
564 | ResultType,
|
565 | QueryArg,
|
566 | BaseQuery extends BaseQueryFn,
|
567 | > = UseMutation<
|
568 | MutationDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
569 | >
|
570 |
|
571 | export type MutationTrigger<D extends MutationDefinition<any, any, any, any>> =
|
572 | {
|
573 | |
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 |
|
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 |
|
589 | (arg: QueryArgFrom<D>): MutationActionCreatorResult<D>
|
590 | }
|
591 |
|
592 | export type TypedMutationTrigger<
|
593 | ResultType,
|
594 | QueryArg,
|
595 | BaseQuery extends BaseQueryFn,
|
596 | > = MutationTrigger<
|
597 | MutationDefinition<QueryArg, BaseQuery, string, ResultType, string>
|
598 | >
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 |
|
605 |
|
606 | const noPendingQueryStateSelector: QueryStateSelector<any, any> = (
|
607 | selected,
|
608 | ) => {
|
609 | if (selected.isUninitialized) {
|
610 | return {
|
611 | ...selected,
|
612 | isUninitialized: false,
|
613 | isFetching: true,
|
614 | isLoading: selected.data !== undefined ? false : true,
|
615 | status: QueryStatus.pending,
|
616 | } as any
|
617 | }
|
618 | return selected
|
619 | }
|
620 |
|
621 | type GenericPrefetchThunk = (
|
622 | endpointName: any,
|
623 | arg: any,
|
624 | options: PrefetchOptions,
|
625 | ) => ThunkAction<void, any, any, UnknownAction>
|
626 |
|
627 |
|
628 |
|
629 |
|
630 |
|
631 |
|
632 |
|
633 |
|
634 |
|
635 | export function buildHooks<Definitions extends EndpointDefinitions>({
|
636 | api,
|
637 | moduleOptions: {
|
638 | batch,
|
639 | hooks: { useDispatch, useSelector, useStore },
|
640 | unstable__sideEffectsInRender,
|
641 | createSelector,
|
642 | },
|
643 | serializeQueryArgs,
|
644 | context,
|
645 | }: {
|
646 | api: Api<any, Definitions, any, any, CoreModule>
|
647 | moduleOptions: Required<ReactHooksModuleOptions>
|
648 | serializeQueryArgs: SerializeQueryArgs<any>
|
649 | context: ApiContext<Definitions>
|
650 | }) {
|
651 | const usePossiblyImmediateEffect: (
|
652 | effect: () => void | undefined,
|
653 | deps?: DependencyList,
|
654 | ) => void = unstable__sideEffectsInRender ? (cb) => cb() : useEffect
|
655 |
|
656 | return { buildQueryHooks, buildMutationHook, usePrefetch }
|
657 |
|
658 | function queryStatePreSelector(
|
659 | currentState: QueryResultSelectorResult<any>,
|
660 | lastResult: UseQueryStateDefaultResult<any> | undefined,
|
661 | queryArgs: any,
|
662 | ): UseQueryStateDefaultResult<any> {
|
663 |
|
664 |
|
665 |
|
666 | if (lastResult?.endpointName && currentState.isUninitialized) {
|
667 | const { endpointName } = lastResult
|
668 | const endpointDefinition = context.endpointDefinitions[endpointName]
|
669 | if (
|
670 | serializeQueryArgs({
|
671 | queryArgs: lastResult.originalArgs,
|
672 | endpointDefinition,
|
673 | endpointName,
|
674 | }) ===
|
675 | serializeQueryArgs({
|
676 | queryArgs,
|
677 | endpointDefinition,
|
678 | endpointName,
|
679 | })
|
680 | )
|
681 | lastResult = undefined
|
682 | }
|
683 |
|
684 |
|
685 | let data = currentState.isSuccess ? currentState.data : lastResult?.data
|
686 | if (data === undefined) data = currentState.data
|
687 |
|
688 | const hasData = data !== undefined
|
689 |
|
690 |
|
691 | const isFetching = currentState.isLoading
|
692 |
|
693 | const isLoading = (!lastResult || lastResult.isLoading || lastResult.isUninitialized) && !hasData && isFetching
|
694 |
|
695 | const isSuccess = currentState.isSuccess || (isFetching && hasData)
|
696 |
|
697 | return {
|
698 | ...currentState,
|
699 | data,
|
700 | currentData: currentState.data,
|
701 | isFetching,
|
702 | isLoading,
|
703 | isSuccess,
|
704 | } as UseQueryStateDefaultResult<any>
|
705 | }
|
706 |
|
707 | function usePrefetch<EndpointName extends QueryKeys<Definitions>>(
|
708 | endpointName: EndpointName,
|
709 | defaultOptions?: PrefetchOptions,
|
710 | ) {
|
711 | const dispatch = useDispatch<ThunkDispatch<any, any, UnknownAction>>()
|
712 | const stableDefaultOptions = useShallowStableValue(defaultOptions)
|
713 |
|
714 | return useCallback(
|
715 | (arg: any, options?: PrefetchOptions) =>
|
716 | dispatch(
|
717 | (api.util.prefetch as GenericPrefetchThunk)(endpointName, arg, {
|
718 | ...stableDefaultOptions,
|
719 | ...options,
|
720 | }),
|
721 | ),
|
722 | [endpointName, dispatch, stableDefaultOptions],
|
723 | )
|
724 | }
|
725 |
|
726 | function buildQueryHooks(name: string): QueryHooks<any> {
|
727 | const useQuerySubscription: UseQuerySubscription<any> = (
|
728 | arg: any,
|
729 | {
|
730 | refetchOnReconnect,
|
731 | refetchOnFocus,
|
732 | refetchOnMountOrArgChange,
|
733 | skip = false,
|
734 | pollingInterval = 0,
|
735 | skipPollingIfUnfocused = false,
|
736 | } = {},
|
737 | ) => {
|
738 | const { initiate } = api.endpoints[name] as ApiEndpointQuery<
|
739 | QueryDefinition<any, any, any, any, any>,
|
740 | Definitions
|
741 | >
|
742 | const dispatch = useDispatch<ThunkDispatch<any, any, UnknownAction>>()
|
743 |
|
744 |
|
745 | |
746 |
|
747 |
|
748 | const subscriptionSelectorsRef = useRef<
|
749 | SubscriptionSelectors | undefined
|
750 | >(undefined)
|
751 |
|
752 | if (!subscriptionSelectorsRef.current) {
|
753 | const returnedValue = dispatch(
|
754 | api.internalActions.internal_getRTKQSubscriptions(),
|
755 | )
|
756 |
|
757 | if (process.env.NODE_ENV !== 'production') {
|
758 | if (
|
759 | typeof returnedValue !== 'object' ||
|
760 | typeof returnedValue?.type === 'string'
|
761 | ) {
|
762 | throw new Error(
|
763 | `Warning: Middleware for RTK-Query API at reducerPath "${api.reducerPath}" has not been added to the store.
|
764 | You must add the middleware for RTK-Query to function correctly!`,
|
765 | )
|
766 | }
|
767 | }
|
768 |
|
769 | subscriptionSelectorsRef.current =
|
770 | returnedValue as unknown as SubscriptionSelectors
|
771 | }
|
772 | const stableArg = useStableQueryArgs(
|
773 | skip ? skipToken : arg,
|
774 |
|
775 |
|
776 |
|
777 |
|
778 |
|
779 | defaultSerializeQueryArgs,
|
780 | context.endpointDefinitions[name],
|
781 | name,
|
782 | )
|
783 | const stableSubscriptionOptions = useShallowStableValue({
|
784 | refetchOnReconnect,
|
785 | refetchOnFocus,
|
786 | pollingInterval,
|
787 | skipPollingIfUnfocused,
|
788 | })
|
789 |
|
790 | const lastRenderHadSubscription = useRef(false)
|
791 |
|
792 |
|
793 | |
794 |
|
795 |
|
796 | const promiseRef = useRef<QueryActionCreatorResult<any> | undefined>(
|
797 | undefined,
|
798 | )
|
799 |
|
800 | let { queryCacheKey, requestId } = promiseRef.current || {}
|
801 |
|
802 |
|
803 |
|
804 | let currentRenderHasSubscription = false
|
805 | if (queryCacheKey && requestId) {
|
806 | currentRenderHasSubscription =
|
807 | subscriptionSelectorsRef.current.isRequestSubscribed(
|
808 | queryCacheKey,
|
809 | requestId,
|
810 | )
|
811 | }
|
812 |
|
813 | const subscriptionRemoved =
|
814 | !currentRenderHasSubscription && lastRenderHadSubscription.current
|
815 |
|
816 | usePossiblyImmediateEffect(() => {
|
817 | lastRenderHadSubscription.current = currentRenderHasSubscription
|
818 | })
|
819 |
|
820 | usePossiblyImmediateEffect((): void | undefined => {
|
821 | if (subscriptionRemoved) {
|
822 | promiseRef.current = undefined
|
823 | }
|
824 | }, [subscriptionRemoved])
|
825 |
|
826 | usePossiblyImmediateEffect((): void | undefined => {
|
827 | const lastPromise = promiseRef.current
|
828 | if (
|
829 | typeof process !== 'undefined' &&
|
830 | process.env.NODE_ENV === 'removeMeOnCompilation'
|
831 | ) {
|
832 |
|
833 | console.log(subscriptionRemoved)
|
834 | }
|
835 |
|
836 | if (stableArg === skipToken) {
|
837 | lastPromise?.unsubscribe()
|
838 | promiseRef.current = undefined
|
839 | return
|
840 | }
|
841 |
|
842 | const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions
|
843 |
|
844 | if (!lastPromise || lastPromise.arg !== stableArg) {
|
845 | lastPromise?.unsubscribe()
|
846 | const promise = dispatch(
|
847 | initiate(stableArg, {
|
848 | subscriptionOptions: stableSubscriptionOptions,
|
849 | forceRefetch: refetchOnMountOrArgChange,
|
850 | }),
|
851 | )
|
852 |
|
853 | promiseRef.current = promise
|
854 | } else if (stableSubscriptionOptions !== lastSubscriptionOptions) {
|
855 | lastPromise.updateSubscriptionOptions(stableSubscriptionOptions)
|
856 | }
|
857 | }, [
|
858 | dispatch,
|
859 | initiate,
|
860 | refetchOnMountOrArgChange,
|
861 | stableArg,
|
862 | stableSubscriptionOptions,
|
863 | subscriptionRemoved,
|
864 | ])
|
865 |
|
866 | useEffect(() => {
|
867 | return () => {
|
868 | promiseRef.current?.unsubscribe()
|
869 | promiseRef.current = undefined
|
870 | }
|
871 | }, [])
|
872 |
|
873 | return useMemo(
|
874 | () => ({
|
875 | |
876 |
|
877 |
|
878 | refetch: () => {
|
879 | if (!promiseRef.current)
|
880 | throw new Error(
|
881 | 'Cannot refetch a query that has not been started yet.',
|
882 | )
|
883 | return promiseRef.current?.refetch()
|
884 | },
|
885 | }),
|
886 | [],
|
887 | )
|
888 | }
|
889 |
|
890 | const useLazyQuerySubscription: UseLazyQuerySubscription<any> = ({
|
891 | refetchOnReconnect,
|
892 | refetchOnFocus,
|
893 | pollingInterval = 0,
|
894 | skipPollingIfUnfocused = false,
|
895 | } = {}) => {
|
896 | const { initiate } = api.endpoints[name] as ApiEndpointQuery<
|
897 | QueryDefinition<any, any, any, any, any>,
|
898 | Definitions
|
899 | >
|
900 | const dispatch = useDispatch<ThunkDispatch<any, any, UnknownAction>>()
|
901 |
|
902 | const [arg, setArg] = useState<any>(UNINITIALIZED_VALUE)
|
903 |
|
904 |
|
905 | |
906 |
|
907 |
|
908 | const promiseRef = useRef<QueryActionCreatorResult<any> | undefined>(
|
909 | undefined,
|
910 | )
|
911 |
|
912 | const stableSubscriptionOptions = useShallowStableValue({
|
913 | refetchOnReconnect,
|
914 | refetchOnFocus,
|
915 | pollingInterval,
|
916 | skipPollingIfUnfocused,
|
917 | })
|
918 |
|
919 | usePossiblyImmediateEffect(() => {
|
920 | const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions
|
921 |
|
922 | if (stableSubscriptionOptions !== lastSubscriptionOptions) {
|
923 | promiseRef.current?.updateSubscriptionOptions(
|
924 | stableSubscriptionOptions,
|
925 | )
|
926 | }
|
927 | }, [stableSubscriptionOptions])
|
928 |
|
929 | const subscriptionOptionsRef = useRef(stableSubscriptionOptions)
|
930 | usePossiblyImmediateEffect(() => {
|
931 | subscriptionOptionsRef.current = stableSubscriptionOptions
|
932 | }, [stableSubscriptionOptions])
|
933 |
|
934 | const trigger = useCallback(
|
935 | function (arg: any, preferCacheValue = false) {
|
936 | let promise: QueryActionCreatorResult<any>
|
937 |
|
938 | batch(() => {
|
939 | promiseRef.current?.unsubscribe()
|
940 |
|
941 | promiseRef.current = promise = dispatch(
|
942 | initiate(arg, {
|
943 | subscriptionOptions: subscriptionOptionsRef.current,
|
944 | forceRefetch: !preferCacheValue,
|
945 | }),
|
946 | )
|
947 |
|
948 | setArg(arg)
|
949 | })
|
950 |
|
951 | return promise!
|
952 | },
|
953 | [dispatch, initiate],
|
954 | )
|
955 |
|
956 |
|
957 | useEffect(() => {
|
958 | return () => {
|
959 | promiseRef?.current?.unsubscribe()
|
960 | }
|
961 | }, [])
|
962 |
|
963 |
|
964 | useEffect(() => {
|
965 | if (arg !== UNINITIALIZED_VALUE && !promiseRef.current) {
|
966 | trigger(arg, true)
|
967 | }
|
968 | }, [arg, trigger])
|
969 |
|
970 | return useMemo(() => [trigger, arg] as const, [trigger, arg])
|
971 | }
|
972 |
|
973 | const useQueryState: UseQueryState<any> = (
|
974 | arg: any,
|
975 | { skip = false, selectFromResult } = {},
|
976 | ) => {
|
977 | const { select } = api.endpoints[name] as ApiEndpointQuery<
|
978 | QueryDefinition<any, any, any, any, any>,
|
979 | Definitions
|
980 | >
|
981 | const stableArg = useStableQueryArgs(
|
982 | skip ? skipToken : arg,
|
983 | serializeQueryArgs,
|
984 | context.endpointDefinitions[name],
|
985 | name,
|
986 | )
|
987 |
|
988 | type ApiRootState = Parameters<ReturnType<typeof select>>[0]
|
989 |
|
990 | const lastValue = useRef<any>(undefined)
|
991 |
|
992 | const selectDefaultResult: Selector<ApiRootState, any, [any]> = useMemo(
|
993 | () =>
|
994 | createSelector(
|
995 | [
|
996 | select(stableArg),
|
997 | (_: ApiRootState, lastResult: any) => lastResult,
|
998 | (_: ApiRootState) => stableArg,
|
999 | ],
|
1000 | queryStatePreSelector,
|
1001 | {
|
1002 | memoizeOptions: {
|
1003 | resultEqualityCheck: shallowEqual,
|
1004 | },
|
1005 | },
|
1006 | ),
|
1007 | [select, stableArg],
|
1008 | )
|
1009 |
|
1010 | const querySelector: Selector<ApiRootState, any, [any]> = useMemo(
|
1011 | () =>
|
1012 | selectFromResult
|
1013 | ? createSelector([selectDefaultResult], selectFromResult, {
|
1014 | devModeChecks: { identityFunctionCheck: 'never' },
|
1015 | })
|
1016 | : selectDefaultResult,
|
1017 | [selectDefaultResult, selectFromResult],
|
1018 | )
|
1019 |
|
1020 | const currentState = useSelector(
|
1021 | (state: RootState<Definitions, any, any>) =>
|
1022 | querySelector(state, lastValue.current),
|
1023 | shallowEqual,
|
1024 | )
|
1025 |
|
1026 | const store = useStore<RootState<Definitions, any, any>>()
|
1027 | const newLastValue = selectDefaultResult(
|
1028 | store.getState(),
|
1029 | lastValue.current,
|
1030 | )
|
1031 | useIsomorphicLayoutEffect(() => {
|
1032 | lastValue.current = newLastValue
|
1033 | }, [newLastValue])
|
1034 |
|
1035 | return currentState
|
1036 | }
|
1037 |
|
1038 | return {
|
1039 | useQueryState,
|
1040 | useQuerySubscription,
|
1041 | useLazyQuerySubscription,
|
1042 | useLazyQuery(options) {
|
1043 | const [trigger, arg] = useLazyQuerySubscription(options)
|
1044 | const queryStateResults = useQueryState(arg, {
|
1045 | ...options,
|
1046 | skip: arg === UNINITIALIZED_VALUE,
|
1047 | })
|
1048 |
|
1049 | const info = useMemo(() => ({ lastArg: arg }), [arg])
|
1050 | return useMemo(
|
1051 | () => [trigger, queryStateResults, info],
|
1052 | [trigger, queryStateResults, info],
|
1053 | )
|
1054 | },
|
1055 | useQuery(arg, options) {
|
1056 | const querySubscriptionResults = useQuerySubscription(arg, options)
|
1057 | const queryStateResults = useQueryState(arg, {
|
1058 | selectFromResult:
|
1059 | arg === skipToken || options?.skip
|
1060 | ? undefined
|
1061 | : noPendingQueryStateSelector,
|
1062 | ...options,
|
1063 | })
|
1064 |
|
1065 | const { data, status, isLoading, isSuccess, isError, error } =
|
1066 | queryStateResults
|
1067 | useDebugValue({ data, status, isLoading, isSuccess, isError, error })
|
1068 |
|
1069 | return useMemo(
|
1070 | () => ({ ...queryStateResults, ...querySubscriptionResults }),
|
1071 | [queryStateResults, querySubscriptionResults],
|
1072 | )
|
1073 | },
|
1074 | }
|
1075 | }
|
1076 |
|
1077 | function buildMutationHook(name: string): UseMutation<any> {
|
1078 | return ({ selectFromResult, fixedCacheKey } = {}) => {
|
1079 | const { select, initiate } = api.endpoints[name] as ApiEndpointMutation<
|
1080 | MutationDefinition<any, any, any, any, any>,
|
1081 | Definitions
|
1082 | >
|
1083 | const dispatch = useDispatch<ThunkDispatch<any, any, UnknownAction>>()
|
1084 | const [promise, setPromise] = useState<MutationActionCreatorResult<any>>()
|
1085 |
|
1086 | useEffect(
|
1087 | () => () => {
|
1088 | if (!promise?.arg.fixedCacheKey) {
|
1089 | promise?.reset()
|
1090 | }
|
1091 | },
|
1092 | [promise],
|
1093 | )
|
1094 |
|
1095 | const triggerMutation = useCallback(
|
1096 | function (arg: Parameters<typeof initiate>['0']) {
|
1097 | const promise = dispatch(initiate(arg, { fixedCacheKey }))
|
1098 | setPromise(promise)
|
1099 | return promise
|
1100 | },
|
1101 | [dispatch, initiate, fixedCacheKey],
|
1102 | )
|
1103 |
|
1104 | const { requestId } = promise || {}
|
1105 | const selectDefaultResult = useMemo(
|
1106 | () => select({ fixedCacheKey, requestId: promise?.requestId }),
|
1107 | [fixedCacheKey, promise, select],
|
1108 | )
|
1109 | const mutationSelector = useMemo(
|
1110 | (): Selector<RootState<Definitions, any, any>, any> =>
|
1111 | selectFromResult
|
1112 | ? createSelector([selectDefaultResult], selectFromResult)
|
1113 | : selectDefaultResult,
|
1114 | [selectFromResult, selectDefaultResult],
|
1115 | )
|
1116 |
|
1117 | const currentState = useSelector(mutationSelector, shallowEqual)
|
1118 | const originalArgs =
|
1119 | fixedCacheKey == null ? promise?.arg.originalArgs : undefined
|
1120 | const reset = useCallback(() => {
|
1121 | batch(() => {
|
1122 | if (promise) {
|
1123 | setPromise(undefined)
|
1124 | }
|
1125 | if (fixedCacheKey) {
|
1126 | dispatch(
|
1127 | api.internalActions.removeMutationResult({
|
1128 | requestId,
|
1129 | fixedCacheKey,
|
1130 | }),
|
1131 | )
|
1132 | }
|
1133 | })
|
1134 | }, [dispatch, fixedCacheKey, promise, requestId])
|
1135 |
|
1136 | const {
|
1137 | endpointName,
|
1138 | data,
|
1139 | status,
|
1140 | isLoading,
|
1141 | isSuccess,
|
1142 | isError,
|
1143 | error,
|
1144 | } = currentState
|
1145 | useDebugValue({
|
1146 | endpointName,
|
1147 | data,
|
1148 | status,
|
1149 | isLoading,
|
1150 | isSuccess,
|
1151 | isError,
|
1152 | error,
|
1153 | })
|
1154 |
|
1155 | const finalState = useMemo(
|
1156 | () => ({ ...currentState, originalArgs, reset }),
|
1157 | [currentState, originalArgs, reset],
|
1158 | )
|
1159 |
|
1160 | return useMemo(
|
1161 | () => [triggerMutation, finalState] as const,
|
1162 | [triggerMutation, finalState],
|
1163 | )
|
1164 | }
|
1165 | }
|
1166 | }
|