source: node_modules/@reduxjs/toolkit/src/query/react/buildHooks.ts

Last change on this file was a762898, checked in by istevanoska <ilinastevanoska@…>, 5 months ago

Added visualizations

  • Property mode set to 100644
File size: 71.8 KB
Line 
1import type {
2 Selector,
3 ThunkAction,
4 ThunkDispatch,
5 UnknownAction,
6} from '@reduxjs/toolkit'
7import type {
8 Api,
9 ApiContext,
10 ApiEndpointInfiniteQuery,
11 ApiEndpointMutation,
12 ApiEndpointQuery,
13 BaseQueryFn,
14 CoreModule,
15 EndpointDefinitions,
16 InfiniteQueryActionCreatorResult,
17 InfiniteQueryArgFrom,
18 InfiniteQueryDefinition,
19 InfiniteQueryResultSelectorResult,
20 InfiniteQuerySubState,
21 MutationActionCreatorResult,
22 MutationDefinition,
23 MutationResultSelectorResult,
24 PageParamFrom,
25 PrefetchOptions,
26 QueryActionCreatorResult,
27 QueryArgFrom,
28 QueryCacheKey,
29 QueryDefinition,
30 QueryKeys,
31 QueryResultSelectorResult,
32 QuerySubState,
33 ResultTypeFrom,
34 RootState,
35 SerializeQueryArgs,
36 SkipToken,
37 SubscriptionOptions,
38 TSHelpersId,
39 TSHelpersNoInfer,
40 TSHelpersOverride,
41} from '@reduxjs/toolkit/query'
42import { QueryStatus, skipToken } from './rtkqImports'
43import type { DependencyList } from 'react'
44import {
45 useCallback,
46 useDebugValue,
47 useEffect,
48 useLayoutEffect,
49 useMemo,
50 useRef,
51 useState,
52} from './reactImports'
53import { shallowEqual } from './reactReduxImports'
54
55import type { SubscriptionSelectors } from '../core/buildMiddleware/index'
56import type { InfiniteData, InfiniteQueryConfigOptions } from '../core/index'
57import type { UninitializedValue } from './constants'
58import { UNINITIALIZED_VALUE } from './constants'
59import type { ReactHooksModuleOptions } from './module'
60import { useStableQueryArgs } from './useSerializedStableValue'
61import { useShallowStableValue } from './useShallowStableValue'
62import type { InfiniteQueryDirection } from '../core/apiState'
63import { isInfiniteQueryDefinition } from '../endpointDefinitions'
64import type { StartInfiniteQueryActionCreator } from '../core/buildInitiate'
65
66// Copy-pasted from React-Redux
67const canUseDOM = () =>
68 !!(
69 typeof window !== 'undefined' &&
70 typeof window.document !== 'undefined' &&
71 typeof window.document.createElement !== 'undefined'
72 )
73
74const isDOM = /* @__PURE__ */ canUseDOM()
75
76// Under React Native, we know that we always want to use useLayoutEffect
77
78const isRunningInReactNative = () =>
79 typeof navigator !== 'undefined' && navigator.product === 'ReactNative'
80
81const isReactNative = /* @__PURE__ */ isRunningInReactNative()
82
83const getUseIsomorphicLayoutEffect = () =>
84 isDOM || isReactNative ? useLayoutEffect : useEffect
85
86export const useIsomorphicLayoutEffect =
87 /* @__PURE__ */ getUseIsomorphicLayoutEffect()
88
89export type QueryHooks<
90 Definition extends QueryDefinition<any, any, any, any, any>,
91> = {
92 useQuery: UseQuery<Definition>
93 useLazyQuery: UseLazyQuery<Definition>
94 useQuerySubscription: UseQuerySubscription<Definition>
95 useLazyQuerySubscription: UseLazyQuerySubscription<Definition>
96 useQueryState: UseQueryState<Definition>
97}
98
99export type InfiniteQueryHooks<
100 Definition extends InfiniteQueryDefinition<any, any, any, any, any>,
101> = {
102 useInfiniteQuery: UseInfiniteQuery<Definition>
103 useInfiniteQuerySubscription: UseInfiniteQuerySubscription<Definition>
104 useInfiniteQueryState: UseInfiniteQueryState<Definition>
105}
106
107export type MutationHooks<
108 Definition extends MutationDefinition<any, any, any, any, any>,
109> = {
110 useMutation: UseMutation<Definition>
111}
112
113/**
114 * A React hook that automatically triggers fetches of data from an endpoint, 'subscribes' the component to the cached data, and reads the request status and cached data from the Redux store. The component will re-render as the loading status changes and the data becomes available.
115 *
116 * The query arg is used as a cache key. Changing the query arg will tell the hook to re-fetch the data if it does not exist in the cache already, and the hook will return the data for that query arg once it's available.
117 *
118 * This hook combines the functionality of both [`useQueryState`](#usequerystate) and [`useQuerySubscription`](#usequerysubscription) together, and is intended to be used in the majority of situations.
119 *
120 * #### Features
121 *
122 * - Automatically triggers requests to retrieve data based on the hook argument and whether cached data exists by default
123 * - 'Subscribes' the component to keep cached data in the store, and 'unsubscribes' when the component unmounts
124 * - Accepts polling/re-fetching options to trigger automatic re-fetches when the corresponding criteria is met
125 * - Returns the latest request status and cached data from the Redux store
126 * - Re-renders as the request status changes and data becomes available
127 */
128export type UseQuery<D extends QueryDefinition<any, any, any, any>> = <
129 R extends Record<string, any> = UseQueryStateDefaultResult<D>,
130>(
131 arg: QueryArgFrom<D> | SkipToken,
132 options?: UseQuerySubscriptionOptions & UseQueryStateOptions<D, R>,
133) => UseQueryHookResult<D, R>
134
135export type TypedUseQuery<
136 ResultType,
137 QueryArg,
138 BaseQuery extends BaseQueryFn,
139> = UseQuery<QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>>
140
141export type UseQueryHookResult<
142 D extends QueryDefinition<any, any, any, any>,
143 R = UseQueryStateDefaultResult<D>,
144> = UseQueryStateResult<D, R> & UseQuerySubscriptionResult<D>
145
146/**
147 * Helper type to manually type the result
148 * of the `useQuery` hook in userland code.
149 */
150export type TypedUseQueryHookResult<
151 ResultType,
152 QueryArg,
153 BaseQuery extends BaseQueryFn,
154 R = UseQueryStateDefaultResult<
155 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
156 >,
157> = TypedUseQueryStateResult<ResultType, QueryArg, BaseQuery, R> &
158 TypedUseQuerySubscriptionResult<ResultType, QueryArg, BaseQuery>
159
160export type UseQuerySubscriptionOptions = SubscriptionOptions & {
161 /**
162 * Prevents a query from automatically running.
163 *
164 * @remarks
165 * When `skip` is true (or `skipToken` is passed in as `arg`):
166 *
167 * - **If the query has cached data:**
168 * * The cached data **will not be used** on the initial load, and will ignore updates from any identical query until the `skip` condition is removed
169 * * The query will have a status of `uninitialized`
170 * * If `skip: false` is set after the initial load, the cached result will be used
171 * - **If the query does not have cached data:**
172 * * The query will have a status of `uninitialized`
173 * * The query will not exist in the state when viewed with the dev tools
174 * * The query will not automatically fetch on mount
175 * * The query will not automatically run when additional components with the same query are added that do run
176 *
177 * @example
178 * ```tsx
179 * // codeblock-meta no-transpile title="Skip example"
180 * const Pokemon = ({ name, skip }: { name: string; skip: boolean }) => {
181 * const { data, error, status } = useGetPokemonByNameQuery(name, {
182 * skip,
183 * });
184 *
185 * return (
186 * <div>
187 * {name} - {status}
188 * </div>
189 * );
190 * };
191 * ```
192 */
193 skip?: boolean
194 /**
195 * Defaults to `false`. This setting allows you to control whether if a cached result is already available, RTK Query will only serve a cached result, or if it should `refetch` when set to `true` or if an adequate amount of time has passed since the last successful query result.
196 * - `false` - Will not cause a query to be performed _unless_ it does not exist yet.
197 * - `true` - Will always refetch when a new subscriber to a query is added. Behaves the same as calling the `refetch` callback or passing `forceRefetch: true` in the action creator.
198 * - `number` - **Value is in seconds**. If a number is provided and there is an existing query in the cache, it will compare the current time vs the last fulfilled timestamp, and only refetch if enough time has elapsed.
199 *
200 * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false.
201 */
202 refetchOnMountOrArgChange?: boolean | number
203}
204
205/**
206 * A React hook that automatically triggers fetches of data from an endpoint, and 'subscribes' the component to the cached data.
207 *
208 * The query arg is used as a cache key. Changing the query arg will tell the hook to re-fetch the data if it does not exist in the cache already.
209 *
210 * Note that this hook does not return a request status or cached data. For that use-case, see [`useQuery`](#usequery) or [`useQueryState`](#usequerystate).
211 *
212 * #### Features
213 *
214 * - Automatically triggers requests to retrieve data based on the hook argument and whether cached data exists by default
215 * - 'Subscribes' the component to keep cached data in the store, and 'unsubscribes' when the component unmounts
216 * - Accepts polling/re-fetching options to trigger automatic re-fetches when the corresponding criteria is met
217 */
218export type UseQuerySubscription<
219 D extends QueryDefinition<any, any, any, any>,
220> = (
221 arg: QueryArgFrom<D> | SkipToken,
222 options?: UseQuerySubscriptionOptions,
223) => UseQuerySubscriptionResult<D>
224
225export type TypedUseQuerySubscription<
226 ResultType,
227 QueryArg,
228 BaseQuery extends BaseQueryFn,
229> = UseQuerySubscription<
230 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
231>
232
233export type UseQuerySubscriptionResult<
234 D extends QueryDefinition<any, any, any, any>,
235> = Pick<QueryActionCreatorResult<D>, 'refetch'>
236
237/**
238 * Helper type to manually type the result
239 * of the `useQuerySubscription` hook in userland code.
240 */
241export type TypedUseQuerySubscriptionResult<
242 ResultType,
243 QueryArg,
244 BaseQuery extends BaseQueryFn,
245> = UseQuerySubscriptionResult<
246 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
247>
248
249export type UseLazyQueryLastPromiseInfo<
250 D extends QueryDefinition<any, any, any, any>,
251> = {
252 lastArg: QueryArgFrom<D>
253}
254
255/**
256 * A React hook similar to [`useQuery`](#usequery), but with manual control over when the data fetching occurs.
257 *
258 * This hook includes the functionality of [`useLazyQuerySubscription`](#uselazyquerysubscription).
259 *
260 * #### Features
261 *
262 * - Manual control over firing a request to retrieve data
263 * - 'Subscribes' the component to keep cached data in the store, and 'unsubscribes' when the component unmounts
264 * - Returns the latest request status and cached data from the Redux store
265 * - Re-renders as the request status changes and data becomes available
266 * - Accepts polling/re-fetching options to trigger automatic re-fetches when the corresponding criteria is met and the fetch has been manually called at least once
267 *
268 * #### Note
269 *
270 * When the trigger function returned from a LazyQuery is called, it always initiates a new request to the server even if there is cached data. Set `preferCacheValue`(the second argument to the function) as `true` if you want it to immediately return a cached value if one exists.
271 */
272export type UseLazyQuery<D extends QueryDefinition<any, any, any, any>> = <
273 R extends Record<string, any> = UseQueryStateDefaultResult<D>,
274>(
275 options?: SubscriptionOptions & Omit<UseQueryStateOptions<D, R>, 'skip'>,
276) => [
277 LazyQueryTrigger<D>,
278 UseLazyQueryStateResult<D, R>,
279 UseLazyQueryLastPromiseInfo<D>,
280]
281
282export type TypedUseLazyQuery<
283 ResultType,
284 QueryArg,
285 BaseQuery extends BaseQueryFn,
286> = UseLazyQuery<
287 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
288>
289
290export type UseLazyQueryStateResult<
291 D extends QueryDefinition<any, any, any, any>,
292 R = UseQueryStateDefaultResult<D>,
293> = UseQueryStateResult<D, R> & {
294 /**
295 * Resets the hook state to its initial `uninitialized` state.
296 * This will also remove the last result from the cache.
297 */
298 reset: () => void
299}
300
301/**
302 * Helper type to manually type the result
303 * of the `useLazyQuery` hook in userland code.
304 */
305export type TypedUseLazyQueryStateResult<
306 ResultType,
307 QueryArg,
308 BaseQuery extends BaseQueryFn,
309 R = UseQueryStateDefaultResult<
310 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
311 >,
312> = UseLazyQueryStateResult<
313 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>,
314 R
315>
316
317export type LazyQueryTrigger<D extends QueryDefinition<any, any, any, any>> = {
318 /**
319 * Triggers a lazy query.
320 *
321 * By default, this will start a new request even if there is already a value in the cache.
322 * If you want to use the cache value and only start a request if there is no cache value, set the second argument to `true`.
323 *
324 * @remarks
325 * If you need to access the error or success payload immediately after a lazy query, you can chain .unwrap().
326 *
327 * @example
328 * ```ts
329 * // codeblock-meta title="Using .unwrap with async await"
330 * try {
331 * const payload = await getUserById(1).unwrap();
332 * console.log('fulfilled', payload)
333 * } catch (error) {
334 * console.error('rejected', error);
335 * }
336 * ```
337 */
338 (
339 arg: QueryArgFrom<D>,
340 preferCacheValue?: boolean,
341 ): QueryActionCreatorResult<D>
342}
343
344export type TypedLazyQueryTrigger<
345 ResultType,
346 QueryArg,
347 BaseQuery extends BaseQueryFn,
348> = LazyQueryTrigger<
349 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
350>
351
352/**
353 * A React hook similar to [`useQuerySubscription`](#usequerysubscription), but with manual control over when the data fetching occurs.
354 *
355 * Note that this hook does not return a request status or cached data. For that use-case, see [`useLazyQuery`](#uselazyquery).
356 *
357 * #### Features
358 *
359 * - Manual control over firing a request to retrieve data
360 * - 'Subscribes' the component to keep cached data in the store, and 'unsubscribes' when the component unmounts
361 * - Accepts polling/re-fetching options to trigger automatic re-fetches when the corresponding criteria is met and the fetch has been manually called at least once
362 */
363export type UseLazyQuerySubscription<
364 D extends QueryDefinition<any, any, any, any>,
365> = (
366 options?: SubscriptionOptions,
367) => readonly [
368 LazyQueryTrigger<D>,
369 QueryArgFrom<D> | UninitializedValue,
370 { reset: () => void },
371]
372
373export type TypedUseLazyQuerySubscription<
374 ResultType,
375 QueryArg,
376 BaseQuery extends BaseQueryFn,
377> = UseLazyQuerySubscription<
378 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
379>
380
381/**
382 * @internal
383 */
384export type QueryStateSelector<
385 R extends Record<string, any>,
386 D extends QueryDefinition<any, any, any, any>,
387> = (state: UseQueryStateDefaultResult<D>) => R
388
389/**
390 * Provides a way to define a strongly-typed version of
391 * {@linkcode QueryStateSelector} for use with a specific query.
392 * This is useful for scenarios where you want to create a "pre-typed"
393 * {@linkcode UseQueryStateOptions.selectFromResult | selectFromResult}
394 * function.
395 *
396 * @example
397 * <caption>#### __Create a strongly-typed `selectFromResult` selector function__</caption>
398 *
399 * ```tsx
400 * import type { TypedQueryStateSelector } from '@reduxjs/toolkit/query/react'
401 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
402 *
403 * type Post = {
404 * id: number
405 * title: string
406 * }
407 *
408 * type PostsApiResponse = {
409 * posts: Post[]
410 * total: number
411 * skip: number
412 * limit: number
413 * }
414 *
415 * type QueryArgument = number | undefined
416 *
417 * type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
418 *
419 * type SelectedResult = Pick<PostsApiResponse, 'posts'>
420 *
421 * const postsApiSlice = createApi({
422 * baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com/posts' }),
423 * reducerPath: 'postsApi',
424 * tagTypes: ['Posts'],
425 * endpoints: (build) => ({
426 * getPosts: build.query<PostsApiResponse, QueryArgument>({
427 * query: (limit = 5) => `?limit=${limit}&select=title`,
428 * }),
429 * }),
430 * })
431 *
432 * const { useGetPostsQuery } = postsApiSlice
433 *
434 * function PostById({ id }: { id: number }) {
435 * const { post } = useGetPostsQuery(undefined, {
436 * selectFromResult: (state) => ({
437 * post: state.data?.posts.find((post) => post.id === id),
438 * }),
439 * })
440 *
441 * return <li>{post?.title}</li>
442 * }
443 *
444 * const EMPTY_ARRAY: Post[] = []
445 *
446 * const typedSelectFromResult: TypedQueryStateSelector<
447 * PostsApiResponse,
448 * QueryArgument,
449 * BaseQueryFunction,
450 * SelectedResult
451 * > = (state) => ({ posts: state.data?.posts ?? EMPTY_ARRAY })
452 *
453 * function PostsList() {
454 * const { posts } = useGetPostsQuery(undefined, {
455 * selectFromResult: typedSelectFromResult,
456 * })
457 *
458 * return (
459 * <div>
460 * <ul>
461 * {posts.map((post) => (
462 * <PostById key={post.id} id={post.id} />
463 * ))}
464 * </ul>
465 * </div>
466 * )
467 * }
468 * ```
469 *
470 * @template ResultType - The type of the result `data` returned by the query.
471 * @template QueryArgumentType - The type of the argument passed into the query.
472 * @template BaseQueryFunctionType - The type of the base query function being used.
473 * @template SelectedResultType - The type of the selected result returned by the __`selectFromResult`__ function.
474 *
475 * @since 2.3.0
476 * @public
477 */
478export type TypedQueryStateSelector<
479 ResultType,
480 QueryArgumentType,
481 BaseQueryFunctionType extends BaseQueryFn,
482 SelectedResultType extends Record<string, any> = UseQueryStateDefaultResult<
483 QueryDefinition<
484 QueryArgumentType,
485 BaseQueryFunctionType,
486 string,
487 ResultType,
488 string
489 >
490 >,
491> = QueryStateSelector<
492 SelectedResultType,
493 QueryDefinition<
494 QueryArgumentType,
495 BaseQueryFunctionType,
496 string,
497 ResultType,
498 string
499 >
500>
501
502/**
503 * A React hook that reads the request status and cached data from the Redux store. The component will re-render as the loading status changes and the data becomes available.
504 *
505 * Note that this hook does not trigger fetching new data. For that use-case, see [`useQuery`](#usequery) or [`useQuerySubscription`](#usequerysubscription).
506 *
507 * #### Features
508 *
509 * - Returns the latest request status and cached data from the Redux store
510 * - Re-renders as the request status changes and data becomes available
511 */
512export type UseQueryState<D extends QueryDefinition<any, any, any, any>> = <
513 R extends Record<string, any> = UseQueryStateDefaultResult<D>,
514>(
515 arg: QueryArgFrom<D> | SkipToken,
516 options?: UseQueryStateOptions<D, R>,
517) => UseQueryStateResult<D, R>
518
519export type TypedUseQueryState<
520 ResultType,
521 QueryArg,
522 BaseQuery extends BaseQueryFn,
523> = UseQueryState<
524 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
525>
526
527/**
528 * @internal
529 */
530export type UseQueryStateOptions<
531 D extends QueryDefinition<any, any, any, any>,
532 R extends Record<string, any>,
533> = {
534 /**
535 * Prevents a query from automatically running.
536 *
537 * @remarks
538 * When skip is true:
539 *
540 * - **If the query has cached data:**
541 * * The cached data **will not be used** on the initial load, and will ignore updates from any identical query until the `skip` condition is removed
542 * * The query will have a status of `uninitialized`
543 * * If `skip: false` is set after skipping the initial load, the cached result will be used
544 * - **If the query does not have cached data:**
545 * * The query will have a status of `uninitialized`
546 * * The query will not exist in the state when viewed with the dev tools
547 * * The query will not automatically fetch on mount
548 * * The query will not automatically run when additional components with the same query are added that do run
549 *
550 * @example
551 * ```ts
552 * // codeblock-meta title="Skip example"
553 * const Pokemon = ({ name, skip }: { name: string; skip: boolean }) => {
554 * const { data, error, status } = useGetPokemonByNameQuery(name, {
555 * skip,
556 * });
557 *
558 * return (
559 * <div>
560 * {name} - {status}
561 * </div>
562 * );
563 * };
564 * ```
565 */
566 skip?: boolean
567 /**
568 * `selectFromResult` allows you to get a specific segment from a query result in a performant manner.
569 * When using this feature, the component will not rerender unless the underlying data of the selected item has changed.
570 * If the selected item is one element in a larger collection, it will disregard changes to elements in the same collection.
571 *
572 * @example
573 * ```ts
574 * // codeblock-meta title="Using selectFromResult to extract a single result"
575 * function PostsList() {
576 * const { data: posts } = api.useGetPostsQuery();
577 *
578 * return (
579 * <ul>
580 * {posts?.data?.map((post) => (
581 * <PostById key={post.id} id={post.id} />
582 * ))}
583 * </ul>
584 * );
585 * }
586 *
587 * function PostById({ id }: { id: number }) {
588 * // Will select the post with the given id, and will only rerender if the given posts data changes
589 * const { post } = api.useGetPostsQuery(undefined, {
590 * selectFromResult: ({ data }) => ({ post: data?.find((post) => post.id === id) }),
591 * });
592 *
593 * return <li>{post?.name}</li>;
594 * }
595 * ```
596 */
597 selectFromResult?: QueryStateSelector<R, D>
598}
599
600/**
601 * Provides a way to define a "pre-typed" version of
602 * {@linkcode UseQueryStateOptions} with specific options for a given query.
603 * This is particularly useful for setting default query behaviors such as
604 * refetching strategies, which can be overridden as needed.
605 *
606 * @example
607 * <caption>#### __Create a `useQuery` hook with default options__</caption>
608 *
609 * ```ts
610 * import type {
611 * SubscriptionOptions,
612 * TypedUseQueryStateOptions,
613 * } from '@reduxjs/toolkit/query/react'
614 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
615 *
616 * type Post = {
617 * id: number
618 * name: string
619 * }
620 *
621 * const api = createApi({
622 * baseQuery: fetchBaseQuery({ baseUrl: '/' }),
623 * tagTypes: ['Post'],
624 * endpoints: (build) => ({
625 * getPosts: build.query<Post[], void>({
626 * query: () => 'posts',
627 * }),
628 * }),
629 * })
630 *
631 * const { useGetPostsQuery } = api
632 *
633 * export const useGetPostsQueryWithDefaults = <
634 * SelectedResult extends Record<string, any>,
635 * >(
636 * overrideOptions: TypedUseQueryStateOptions<
637 * Post[],
638 * void,
639 * ReturnType<typeof fetchBaseQuery>,
640 * SelectedResult
641 * > &
642 * SubscriptionOptions,
643 * ) =>
644 * useGetPostsQuery(undefined, {
645 * // Insert default options here
646 *
647 * refetchOnMountOrArgChange: true,
648 * refetchOnFocus: true,
649 * ...overrideOptions,
650 * })
651 * ```
652 *
653 * @template ResultType - The type of the result `data` returned by the query.
654 * @template QueryArg - The type of the argument passed into the query.
655 * @template BaseQuery - The type of the base query function being used.
656 * @template SelectedResult - The type of the selected result returned by the __`selectFromResult`__ function.
657 *
658 * @since 2.2.8
659 * @public
660 */
661export type TypedUseQueryStateOptions<
662 ResultType,
663 QueryArg,
664 BaseQuery extends BaseQueryFn,
665 SelectedResult extends Record<string, any> = UseQueryStateDefaultResult<
666 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
667 >,
668> = UseQueryStateOptions<
669 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>,
670 SelectedResult
671>
672
673export type UseQueryStateResult<
674 _ extends QueryDefinition<any, any, any, any>,
675 R,
676> = TSHelpersNoInfer<R>
677
678/**
679 * Helper type to manually type the result
680 * of the `useQueryState` hook in userland code.
681 */
682export type TypedUseQueryStateResult<
683 ResultType,
684 QueryArg,
685 BaseQuery extends BaseQueryFn,
686 R = UseQueryStateDefaultResult<
687 QueryDefinition<QueryArg, BaseQuery, string, ResultType, string>
688 >,
689> = TSHelpersNoInfer<R>
690
691type UseQueryStateBaseResult<D extends QueryDefinition<any, any, any, any>> =
692 QuerySubState<D> & {
693 /**
694 * Where `data` tries to hold data as much as possible, also re-using
695 * data from the last arguments passed into the hook, this property
696 * will always contain the received data from the query, for the current query arguments.
697 */
698 currentData?: ResultTypeFrom<D>
699 /**
700 * Query has not started yet.
701 */
702 isUninitialized: false
703 /**
704 * Query is currently loading for the first time. No data yet.
705 */
706 isLoading: false
707 /**
708 * Query is currently fetching, but might have data from an earlier request.
709 */
710 isFetching: false
711 /**
712 * Query has data from a successful load.
713 */
714 isSuccess: false
715 /**
716 * Query is currently in "error" state.
717 */
718 isError: false
719 }
720
721type UseQueryStateUninitialized<D extends QueryDefinition<any, any, any, any>> =
722 TSHelpersOverride<
723 Extract<UseQueryStateBaseResult<D>, { status: QueryStatus.uninitialized }>,
724 { isUninitialized: true }
725 >
726
727type UseQueryStateLoading<D extends QueryDefinition<any, any, any, any>> =
728 TSHelpersOverride<
729 UseQueryStateBaseResult<D>,
730 { isLoading: true; isFetching: boolean; data: undefined }
731 >
732
733type UseQueryStateSuccessFetching<
734 D extends QueryDefinition<any, any, any, any>,
735> = TSHelpersOverride<
736 UseQueryStateBaseResult<D>,
737 {
738 isSuccess: true
739 isFetching: true
740 error: undefined
741 } & {
742 data: ResultTypeFrom<D>
743 } & Required<Pick<UseQueryStateBaseResult<D>, 'fulfilledTimeStamp'>>
744>
745
746type UseQueryStateSuccessNotFetching<
747 D extends QueryDefinition<any, any, any, any>,
748> = TSHelpersOverride<
749 UseQueryStateBaseResult<D>,
750 {
751 isSuccess: true
752 isFetching: false
753 error: undefined
754 } & {
755 data: ResultTypeFrom<D>
756 currentData: ResultTypeFrom<D>
757 } & Required<Pick<UseQueryStateBaseResult<D>, 'fulfilledTimeStamp'>>
758>
759
760type UseQueryStateError<D extends QueryDefinition<any, any, any, any>> =
761 TSHelpersOverride<
762 UseQueryStateBaseResult<D>,
763 { isError: true } & Required<Pick<UseQueryStateBaseResult<D>, 'error'>>
764 >
765
766type UseQueryStateDefaultResult<D extends QueryDefinition<any, any, any, any>> =
767 TSHelpersId<
768 | UseQueryStateUninitialized<D>
769 | UseQueryStateLoading<D>
770 | UseQueryStateSuccessFetching<D>
771 | UseQueryStateSuccessNotFetching<D>
772 | UseQueryStateError<D>
773 > & {
774 /**
775 * @deprecated Included for completeness, but discouraged.
776 * Please use the `isLoading`, `isFetching`, `isSuccess`, `isError`
777 * and `isUninitialized` flags instead
778 */
779 status: QueryStatus
780 }
781
782export type LazyInfiniteQueryTrigger<
783 D extends InfiniteQueryDefinition<any, any, any, any, any>,
784> = {
785 /**
786 * Triggers a lazy query.
787 *
788 * By default, this will start a new request even if there is already a value in the cache.
789 * If you want to use the cache value and only start a request if there is no cache value, set the second argument to `true`.
790 *
791 * @remarks
792 * If you need to access the error or success payload immediately after a lazy query, you can chain .unwrap().
793 *
794 * @example
795 * ```ts
796 * // codeblock-meta title="Using .unwrap with async await"
797 * try {
798 * const payload = await getUserById(1).unwrap();
799 * console.log('fulfilled', payload)
800 * } catch (error) {
801 * console.error('rejected', error);
802 * }
803 * ```
804 */
805 (
806 arg: QueryArgFrom<D>,
807 direction: InfiniteQueryDirection,
808 ): InfiniteQueryActionCreatorResult<D>
809}
810
811export type TypedLazyInfiniteQueryTrigger<
812 ResultType,
813 QueryArg,
814 PageParam,
815 BaseQuery extends BaseQueryFn,
816> = LazyInfiniteQueryTrigger<
817 InfiniteQueryDefinition<
818 QueryArg,
819 PageParam,
820 BaseQuery,
821 string,
822 ResultType,
823 string
824 >
825>
826
827export type UseInfiniteQuerySubscriptionOptions<
828 D extends InfiniteQueryDefinition<any, any, any, any, any>,
829> = SubscriptionOptions & {
830 /**
831 * Prevents a query from automatically running.
832 *
833 * @remarks
834 * When `skip` is true (or `skipToken` is passed in as `arg`):
835 *
836 * - **If the query has cached data:**
837 * * The cached data **will not be used** on the initial load, and will ignore updates from any identical query until the `skip` condition is removed
838 * * The query will have a status of `uninitialized`
839 * * If `skip: false` is set after the initial load, the cached result will be used
840 * - **If the query does not have cached data:**
841 * * The query will have a status of `uninitialized`
842 * * The query will not exist in the state when viewed with the dev tools
843 * * The query will not automatically fetch on mount
844 * * The query will not automatically run when additional components with the same query are added that do run
845 *
846 * @example
847 * ```tsx
848 * // codeblock-meta no-transpile title="Skip example"
849 * const Pokemon = ({ name, skip }: { name: string; skip: boolean }) => {
850 * const { data, error, status } = useGetPokemonByNameQuery(name, {
851 * skip,
852 * });
853 *
854 * return (
855 * <div>
856 * {name} - {status}
857 * </div>
858 * );
859 * };
860 * ```
861 */
862 skip?: boolean
863 /**
864 * Defaults to `false`. This setting allows you to control whether if a cached result is already available, RTK Query will only serve a cached result, or if it should `refetch` when set to `true` or if an adequate amount of time has passed since the last successful query result.
865 * - `false` - Will not cause a query to be performed _unless_ it does not exist yet.
866 * - `true` - Will always refetch when a new subscriber to a query is added. Behaves the same as calling the `refetch` callback or passing `forceRefetch: true` in the action creator.
867 * - `number` - **Value is in seconds**. If a number is provided and there is an existing query in the cache, it will compare the current time vs the last fulfilled timestamp, and only refetch if enough time has elapsed.
868 *
869 * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false.
870 */
871 refetchOnMountOrArgChange?: boolean | number
872 initialPageParam?: PageParamFrom<D>
873 /**
874 * Defaults to `true`. When this is `true` and an infinite query endpoint is refetched
875 * (due to tag invalidation, polling, arg change configuration, or manual refetching),
876 * RTK Query will try to sequentially refetch all pages currently in the cache.
877 * When `false` only the first page will be refetched.
878 *
879 * This option applies to all automatic refetches for this subscription (polling, tag invalidation, etc.).
880 * It can be overridden on a per-call basis using the `refetch()` method.
881 */
882 refetchCachedPages?: boolean
883}
884
885export type TypedUseInfiniteQuerySubscription<
886 ResultType,
887 QueryArg,
888 PageParam,
889 BaseQuery extends BaseQueryFn,
890> = UseInfiniteQuerySubscription<
891 InfiniteQueryDefinition<
892 QueryArg,
893 PageParam,
894 BaseQuery,
895 string,
896 ResultType,
897 string
898 >
899>
900
901export type UseInfiniteQuerySubscriptionResult<
902 D extends InfiniteQueryDefinition<any, any, any, any, any>,
903> = {
904 refetch: (
905 options?: Pick<
906 UseInfiniteQuerySubscriptionOptions<D>,
907 'refetchCachedPages'
908 >,
909 ) => InfiniteQueryActionCreatorResult<D>
910 trigger: LazyInfiniteQueryTrigger<D>
911 fetchNextPage: () => InfiniteQueryActionCreatorResult<D>
912 fetchPreviousPage: () => InfiniteQueryActionCreatorResult<D>
913}
914
915/**
916 * Helper type to manually type the result
917 * of the `useQuerySubscription` hook in userland code.
918 */
919export type TypedUseInfiniteQuerySubscriptionResult<
920 ResultType,
921 QueryArg,
922 PageParam,
923 BaseQuery extends BaseQueryFn,
924> = UseInfiniteQuerySubscriptionResult<
925 InfiniteQueryDefinition<
926 QueryArg,
927 PageParam,
928 BaseQuery,
929 string,
930 ResultType,
931 string
932 >
933>
934
935export type InfiniteQueryStateSelector<
936 R extends Record<string, any>,
937 D extends InfiniteQueryDefinition<any, any, any, any, any>,
938> = (state: UseInfiniteQueryStateDefaultResult<D>) => R
939
940export type TypedInfiniteQueryStateSelector<
941 ResultType,
942 QueryArg,
943 PageParam,
944 BaseQuery extends BaseQueryFn,
945 SelectedResult extends Record<
946 string,
947 any
948 > = UseInfiniteQueryStateDefaultResult<
949 InfiniteQueryDefinition<
950 QueryArg,
951 PageParam,
952 BaseQuery,
953 string,
954 ResultType,
955 string
956 >
957 >,
958> = InfiniteQueryStateSelector<
959 SelectedResult,
960 InfiniteQueryDefinition<
961 QueryArg,
962 PageParam,
963 BaseQuery,
964 string,
965 ResultType,
966 string
967 >
968>
969
970/**
971 * A React hook that automatically triggers fetches of data from an endpoint, 'subscribes' the component to the cached data, and reads the request status and cached data from the Redux store. The component will re-render as the loading status changes and the data becomes available. Additionally, it will cache multiple "pages" worth of responses within a single cache entry, and allows fetching more pages forwards and backwards from the current cached pages.
972 *
973 * The query arg is used as a cache key. Changing the query arg will tell the hook to re-fetch the data if it does not exist in the cache already, and the hook will return the data for that query arg once it's available.
974 *
975 * The `data` field will be a `{pages: Data[], pageParams: PageParam[]}` structure containing all fetched page responses and the corresponding page param values for each page. You may use this to render individual pages, combine all pages into a single infinite list, or other display logic as needed.
976 *
977 * This hook combines the functionality of both [`useInfiniteQueryState`](#useinfinitequerystate) and [`useInfiniteQuerySubscription`](#useinfinitequerysubscription) together, and is intended to be used in the majority of situations.
978 *
979 * As with normal query hooks, `skipToken` is a valid argument that will skip the query from executing.
980 *
981 * By default, the initial request will use the `initialPageParam` value that was defined on the infinite query endpoint. If you want to start from a different value, you can pass `initialPageParam` as part of the hook options to override that initial request value.
982 *
983 * Use the returned `fetchNextPage` and `fetchPreviousPage` methods on the hook result object to trigger fetches forwards and backwards. These will always calculate the next or previous page param based on the current cached pages and the provided `getNext/PreviousPageParam` callbacks defined in the endpoint.
984 *
985 *
986 * #### Features
987 *
988 * - Automatically triggers requests to retrieve data based on the hook argument and whether cached data exists by default
989 * - 'Subscribes' the component to keep cached data in the store, and 'unsubscribes' when the component unmounts
990 * - Caches multiple pages worth of responses, and provides methods to trigger more page fetches forwards and backwards
991 * - Accepts polling/re-fetching options to trigger automatic re-fetches when the corresponding criteria is met
992 * - Returns the latest request status and cached data from the Redux store
993 * - Re-renders as the request status changes and data becomes available
994 */
995export type UseInfiniteQuery<
996 D extends InfiniteQueryDefinition<any, any, any, any, any>,
997> = <R extends Record<string, any> = UseInfiniteQueryStateDefaultResult<D>>(
998 arg: InfiniteQueryArgFrom<D> | SkipToken,
999 options?: UseInfiniteQuerySubscriptionOptions<D> &
1000 UseInfiniteQueryStateOptions<D, R>,
1001) => UseInfiniteQueryHookResult<D, R> &
1002 Pick<
1003 UseInfiniteQuerySubscriptionResult<D>,
1004 'fetchNextPage' | 'fetchPreviousPage'
1005 >
1006
1007export type TypedUseInfiniteQuery<
1008 ResultType,
1009 QueryArg,
1010 PageParam,
1011 BaseQuery extends BaseQueryFn,
1012> = UseInfiniteQuery<
1013 InfiniteQueryDefinition<
1014 QueryArg,
1015 PageParam,
1016 BaseQuery,
1017 string,
1018 ResultType,
1019 string
1020 >
1021>
1022
1023/**
1024 * A React hook that reads the request status and cached data from the Redux store. The component will re-render as the loading status changes and the data becomes available.
1025 *
1026 * Note that this hook does not trigger fetching new data. For that use-case, see [`useInfiniteQuery`](#useinfinitequery) or [`useInfiniteQuerySubscription`](#useinfinitequerysubscription).
1027 *
1028 * #### Features
1029 *
1030 * - Returns the latest request status and cached data from the Redux store
1031 * - Re-renders as the request status changes and data becomes available
1032 */
1033export type UseInfiniteQueryState<
1034 D extends InfiniteQueryDefinition<any, any, any, any, any>,
1035> = <R extends Record<string, any> = UseInfiniteQueryStateDefaultResult<D>>(
1036 arg: InfiniteQueryArgFrom<D> | SkipToken,
1037 options?: UseInfiniteQueryStateOptions<D, R>,
1038) => UseInfiniteQueryStateResult<D, R>
1039
1040export type TypedUseInfiniteQueryState<
1041 ResultType,
1042 QueryArg,
1043 PageParam,
1044 BaseQuery extends BaseQueryFn,
1045> = UseInfiniteQueryState<
1046 InfiniteQueryDefinition<
1047 QueryArg,
1048 PageParam,
1049 BaseQuery,
1050 string,
1051 ResultType,
1052 string
1053 >
1054>
1055
1056/**
1057 * A React hook that automatically triggers fetches of data from an endpoint, and 'subscribes' the component to the cached data. Additionally, it will cache multiple "pages" worth of responses within a single cache entry, and allows fetching more pages forwards and backwards from the current cached pages.
1058 *
1059 * The query arg is used as a cache key. Changing the query arg will tell the hook to re-fetch the data if it does not exist in the cache already.
1060 *
1061 * Note that this hook does not return a request status or cached data. For that use-case, see [`useInfiniteQuery`](#useinfinitequery) or [`useInfiniteQueryState`](#useinfinitequerystate).
1062 *
1063 * #### Features
1064 *
1065 * - Automatically triggers requests to retrieve data based on the hook argument and whether cached data exists by default
1066 * - 'Subscribes' the component to keep cached data in the store, and 'unsubscribes' when the component unmounts
1067 * - Caches multiple pages worth of responses, and provides methods to trigger more page fetches forwards and backwards
1068 * - Accepts polling/re-fetching options to trigger automatic re-fetches when the corresponding criteria is met
1069 */
1070export type UseInfiniteQuerySubscription<
1071 D extends InfiniteQueryDefinition<any, any, any, any, any>,
1072> = (
1073 arg: InfiniteQueryArgFrom<D> | SkipToken,
1074 options?: UseInfiniteQuerySubscriptionOptions<D>,
1075) => UseInfiniteQuerySubscriptionResult<D>
1076
1077export type UseInfiniteQueryHookResult<
1078 D extends InfiniteQueryDefinition<any, any, any, any, any>,
1079 R = UseInfiniteQueryStateDefaultResult<D>,
1080> = UseInfiniteQueryStateResult<D, R> &
1081 Pick<
1082 UseInfiniteQuerySubscriptionResult<D>,
1083 'refetch' | 'fetchNextPage' | 'fetchPreviousPage'
1084 >
1085
1086export type TypedUseInfiniteQueryHookResult<
1087 ResultType,
1088 QueryArg,
1089 PageParam,
1090 BaseQuery extends BaseQueryFn,
1091 R extends Record<string, any> = UseInfiniteQueryStateDefaultResult<
1092 InfiniteQueryDefinition<
1093 QueryArg,
1094 PageParam,
1095 BaseQuery,
1096 string,
1097 ResultType,
1098 string
1099 >
1100 >,
1101> = UseInfiniteQueryHookResult<
1102 InfiniteQueryDefinition<
1103 QueryArg,
1104 PageParam,
1105 BaseQuery,
1106 string,
1107 ResultType,
1108 string
1109 >,
1110 R
1111>
1112
1113export type UseInfiniteQueryStateOptions<
1114 D extends InfiniteQueryDefinition<any, any, any, any, any>,
1115 R extends Record<string, any>,
1116> = {
1117 /**
1118 * Prevents a query from automatically running.
1119 *
1120 * @remarks
1121 * When skip is true:
1122 *
1123 * - **If the query has cached data:**
1124 * * The cached data **will not be used** on the initial load, and will ignore updates from any identical query until the `skip` condition is removed
1125 * * The query will have a status of `uninitialized`
1126 * * If `skip: false` is set after skipping the initial load, the cached result will be used
1127 * - **If the query does not have cached data:**
1128 * * The query will have a status of `uninitialized`
1129 * * The query will not exist in the state when viewed with the dev tools
1130 * * The query will not automatically fetch on mount
1131 * * The query will not automatically run when additional components with the same query are added that do run
1132 *
1133 * @example
1134 * ```ts
1135 * // codeblock-meta title="Skip example"
1136 * const Pokemon = ({ name, skip }: { name: string; skip: boolean }) => {
1137 * const { data, error, status } = useGetPokemonByNameQuery(name, {
1138 * skip,
1139 * });
1140 *
1141 * return (
1142 * <div>
1143 * {name} - {status}
1144 * </div>
1145 * );
1146 * };
1147 * ```
1148 */
1149 skip?: boolean
1150 /**
1151 * `selectFromResult` allows you to get a specific segment from a query result in a performant manner.
1152 * When using this feature, the component will not rerender unless the underlying data of the selected item has changed.
1153 * If the selected item is one element in a larger collection, it will disregard changes to elements in the same collection.
1154 * Note that this should always return an object (not a primitive), as RTKQ adds fields to the return value.
1155 *
1156 * @example
1157 * ```ts
1158 * // codeblock-meta title="Using selectFromResult to extract a single result"
1159 * function PostsList() {
1160 * const { data: posts } = api.useGetPostsQuery();
1161 *
1162 * return (
1163 * <ul>
1164 * {posts?.data?.map((post) => (
1165 * <PostById key={post.id} id={post.id} />
1166 * ))}
1167 * </ul>
1168 * );
1169 * }
1170 *
1171 * function PostById({ id }: { id: number }) {
1172 * // Will select the post with the given id, and will only rerender if the given posts data changes
1173 * const { post } = api.useGetPostsQuery(undefined, {
1174 * selectFromResult: ({ data }) => ({ post: data?.find((post) => post.id === id) }),
1175 * });
1176 *
1177 * return <li>{post?.name}</li>;
1178 * }
1179 * ```
1180 */
1181 selectFromResult?: InfiniteQueryStateSelector<R, D>
1182}
1183
1184export type TypedUseInfiniteQueryStateOptions<
1185 ResultType,
1186 QueryArg,
1187 PageParam,
1188 BaseQuery extends BaseQueryFn,
1189 SelectedResult extends Record<
1190 string,
1191 any
1192 > = UseInfiniteQueryStateDefaultResult<
1193 InfiniteQueryDefinition<
1194 QueryArg,
1195 PageParam,
1196 BaseQuery,
1197 string,
1198 ResultType,
1199 string
1200 >
1201 >,
1202> = UseInfiniteQueryStateOptions<
1203 InfiniteQueryDefinition<
1204 QueryArg,
1205 PageParam,
1206 BaseQuery,
1207 string,
1208 ResultType,
1209 string
1210 >,
1211 SelectedResult
1212>
1213
1214export type UseInfiniteQueryStateResult<
1215 D extends InfiniteQueryDefinition<any, any, any, any, any>,
1216 R = UseInfiniteQueryStateDefaultResult<D>,
1217> = TSHelpersNoInfer<R>
1218
1219export type TypedUseInfiniteQueryStateResult<
1220 ResultType,
1221 QueryArg,
1222 PageParam,
1223 BaseQuery extends BaseQueryFn,
1224 R = UseInfiniteQueryStateDefaultResult<
1225 InfiniteQueryDefinition<
1226 QueryArg,
1227 PageParam,
1228 BaseQuery,
1229 string,
1230 ResultType,
1231 string
1232 >
1233 >,
1234> = UseInfiniteQueryStateResult<
1235 InfiniteQueryDefinition<
1236 QueryArg,
1237 PageParam,
1238 BaseQuery,
1239 string,
1240 ResultType,
1241 string
1242 >,
1243 R
1244>
1245
1246type UseInfiniteQueryStateBaseResult<
1247 D extends InfiniteQueryDefinition<any, any, any, any, any>,
1248> = InfiniteQuerySubState<D> & {
1249 /**
1250 * Where `data` tries to hold data as much as possible, also re-using
1251 * data from the last arguments passed into the hook, this property
1252 * will always contain the received data from the query, for the current query arguments.
1253 */
1254 currentData?: InfiniteData<ResultTypeFrom<D>, PageParamFrom<D>>
1255 /**
1256 * Query has not started yet.
1257 */
1258 isUninitialized: false
1259 /**
1260 * Query is currently loading for the first time. No data yet.
1261 */
1262 isLoading: false
1263 /**
1264 * Query is currently fetching, but might have data from an earlier request.
1265 */
1266 isFetching: false
1267 /**
1268 * Query has data from a successful load.
1269 */
1270 isSuccess: false
1271 /**
1272 * Query is currently in "error" state.
1273 */
1274 isError: false
1275 hasNextPage: boolean
1276 hasPreviousPage: boolean
1277 isFetchingNextPage: boolean
1278 isFetchingPreviousPage: boolean
1279}
1280
1281type UseInfiniteQueryStateDefaultResult<
1282 D extends InfiniteQueryDefinition<any, any, any, any, any>,
1283> = TSHelpersId<
1284 | TSHelpersOverride<
1285 Extract<
1286 UseInfiniteQueryStateBaseResult<D>,
1287 { status: QueryStatus.uninitialized }
1288 >,
1289 { isUninitialized: true }
1290 >
1291 | TSHelpersOverride<
1292 UseInfiniteQueryStateBaseResult<D>,
1293 | { isLoading: true; isFetching: boolean; data: undefined }
1294 | ({
1295 isSuccess: true
1296 isFetching: true
1297 error: undefined
1298 } & Required<
1299 Pick<
1300 UseInfiniteQueryStateBaseResult<D>,
1301 'data' | 'fulfilledTimeStamp'
1302 >
1303 >)
1304 | ({
1305 isSuccess: true
1306 isFetching: false
1307 error: undefined
1308 } & Required<
1309 Pick<
1310 UseInfiniteQueryStateBaseResult<D>,
1311 'data' | 'fulfilledTimeStamp' | 'currentData'
1312 >
1313 >)
1314 | ({ isError: true } & Required<
1315 Pick<UseInfiniteQueryStateBaseResult<D>, 'error'>
1316 >)
1317 >
1318> & {
1319 /**
1320 * @deprecated Included for completeness, but discouraged.
1321 * Please use the `isLoading`, `isFetching`, `isSuccess`, `isError`
1322 * and `isUninitialized` flags instead
1323 */
1324 status: QueryStatus
1325}
1326
1327export type MutationStateSelector<
1328 R extends Record<string, any>,
1329 D extends MutationDefinition<any, any, any, any>,
1330> = (state: MutationResultSelectorResult<D>) => R
1331
1332export type UseMutationStateOptions<
1333 D extends MutationDefinition<any, any, any, any>,
1334 R extends Record<string, any>,
1335> = {
1336 selectFromResult?: MutationStateSelector<R, D>
1337 fixedCacheKey?: string
1338}
1339
1340export type UseMutationStateResult<
1341 D extends MutationDefinition<any, any, any, any>,
1342 R,
1343> = TSHelpersNoInfer<R> & {
1344 originalArgs?: QueryArgFrom<D>
1345 /**
1346 * Resets the hook state to its initial `uninitialized` state.
1347 * This will also remove the last result from the cache.
1348 */
1349 reset: () => void
1350}
1351
1352/**
1353 * Helper type to manually type the result
1354 * of the `useMutation` hook in userland code.
1355 */
1356export type TypedUseMutationResult<
1357 ResultType,
1358 QueryArg,
1359 BaseQuery extends BaseQueryFn,
1360 R = MutationResultSelectorResult<
1361 MutationDefinition<QueryArg, BaseQuery, string, ResultType, string>
1362 >,
1363> = UseMutationStateResult<
1364 MutationDefinition<QueryArg, BaseQuery, string, ResultType, string>,
1365 R
1366>
1367
1368/**
1369 * A React hook that lets you trigger an update request for a given endpoint, and subscribes the component to read the request status from the Redux store. The component will re-render as the loading status changes.
1370 *
1371 * #### Features
1372 *
1373 * - Manual control over firing a request to alter data on the server or possibly invalidate the cache
1374 * - 'Subscribes' the component to keep cached data in the store, and 'unsubscribes' when the component unmounts
1375 * - Returns the latest request status and cached data from the Redux store
1376 * - Re-renders as the request status changes and data becomes available
1377 */
1378export type UseMutation<D extends MutationDefinition<any, any, any, any>> = <
1379 R extends Record<string, any> = MutationResultSelectorResult<D>,
1380>(
1381 options?: UseMutationStateOptions<D, R>,
1382) => readonly [MutationTrigger<D>, UseMutationStateResult<D, R>]
1383
1384export type TypedUseMutation<
1385 ResultType,
1386 QueryArg,
1387 BaseQuery extends BaseQueryFn,
1388> = UseMutation<
1389 MutationDefinition<QueryArg, BaseQuery, string, ResultType, string>
1390>
1391
1392export type MutationTrigger<D extends MutationDefinition<any, any, any, any>> =
1393 {
1394 /**
1395 * Triggers the mutation and returns a Promise.
1396 * @remarks
1397 * If you need to access the error or success payload immediately after a mutation, you can chain .unwrap().
1398 *
1399 * @example
1400 * ```ts
1401 * // codeblock-meta title="Using .unwrap with async await"
1402 * try {
1403 * const payload = await addPost({ id: 1, name: 'Example' }).unwrap();
1404 * console.log('fulfilled', payload)
1405 * } catch (error) {
1406 * console.error('rejected', error);
1407 * }
1408 * ```
1409 */
1410 (arg: QueryArgFrom<D>): MutationActionCreatorResult<D>
1411 }
1412
1413export type TypedMutationTrigger<
1414 ResultType,
1415 QueryArg,
1416 BaseQuery extends BaseQueryFn,
1417> = MutationTrigger<
1418 MutationDefinition<QueryArg, BaseQuery, string, ResultType, string>
1419>
1420
1421/**
1422 * Wrapper around `defaultQueryStateSelector` to be used in `useQuery`.
1423 * We want the initial render to already come back with
1424 * `{ isUninitialized: false, isFetching: true, isLoading: true }`
1425 * to prevent that the library user has to do an additional check for `isUninitialized`/
1426 */
1427const noPendingQueryStateSelector: QueryStateSelector<any, any> = (
1428 selected,
1429) => {
1430 if (selected.isUninitialized) {
1431 return {
1432 ...selected,
1433 isUninitialized: false,
1434 isFetching: true,
1435 isLoading: selected.data !== undefined ? false : true,
1436 // This is the one place where we still have to use `QueryStatus` as an enum,
1437 // since it's the only reference in the React package and not in the core.
1438 status: QueryStatus.pending,
1439 } as any
1440 }
1441 return selected
1442}
1443
1444function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
1445 const ret: any = {}
1446 keys.forEach((key) => {
1447 ret[key] = obj[key]
1448 })
1449 return ret
1450}
1451
1452const COMMON_HOOK_DEBUG_FIELDS = [
1453 'data',
1454 'status',
1455 'isLoading',
1456 'isSuccess',
1457 'isError',
1458 'error',
1459] as const
1460
1461type GenericPrefetchThunk = (
1462 endpointName: any,
1463 arg: any,
1464 options: PrefetchOptions,
1465) => ThunkAction<void, any, any, UnknownAction>
1466
1467/**
1468 *
1469 * @param opts.api - An API with defined endpoints to create hooks for
1470 * @param opts.moduleOptions.batch - The version of the `batchedUpdates` function to be used
1471 * @param opts.moduleOptions.useDispatch - The version of the `useDispatch` hook to be used
1472 * @param opts.moduleOptions.useSelector - The version of the `useSelector` hook to be used
1473 * @returns An object containing functions to generate hooks based on an endpoint
1474 */
1475export function buildHooks<Definitions extends EndpointDefinitions>({
1476 api,
1477 moduleOptions: {
1478 batch,
1479 hooks: { useDispatch, useSelector, useStore },
1480 unstable__sideEffectsInRender,
1481 createSelector,
1482 },
1483 serializeQueryArgs,
1484 context,
1485}: {
1486 api: Api<any, Definitions, any, any, CoreModule>
1487 moduleOptions: Required<ReactHooksModuleOptions>
1488 serializeQueryArgs: SerializeQueryArgs<any>
1489 context: ApiContext<Definitions>
1490}) {
1491 const usePossiblyImmediateEffect: (
1492 effect: () => void | undefined,
1493 deps?: DependencyList,
1494 ) => void = unstable__sideEffectsInRender ? (cb) => cb() : useEffect
1495
1496 type UnsubscribePromiseRef = React.RefObject<
1497 { unsubscribe?: () => void } | undefined
1498 >
1499
1500 const unsubscribePromiseRef = (ref: UnsubscribePromiseRef) =>
1501 ref.current?.unsubscribe?.()
1502
1503 const endpointDefinitions = context.endpointDefinitions
1504
1505 return {
1506 buildQueryHooks,
1507 buildInfiniteQueryHooks,
1508 buildMutationHook,
1509 usePrefetch,
1510 }
1511
1512 function queryStatePreSelector(
1513 currentState: QueryResultSelectorResult<any>,
1514 lastResult: UseQueryStateDefaultResult<any> | undefined,
1515 queryArgs: any,
1516 ): UseQueryStateDefaultResult<any> {
1517 // if we had a last result and the current result is uninitialized,
1518 // we might have called `api.util.resetApiState`
1519 // in this case, reset the hook
1520 if (lastResult?.endpointName && currentState.isUninitialized) {
1521 const { endpointName } = lastResult
1522 const endpointDefinition = endpointDefinitions[endpointName]
1523 if (
1524 queryArgs !== skipToken &&
1525 serializeQueryArgs({
1526 queryArgs: lastResult.originalArgs,
1527 endpointDefinition,
1528 endpointName,
1529 }) ===
1530 serializeQueryArgs({
1531 queryArgs,
1532 endpointDefinition,
1533 endpointName,
1534 })
1535 )
1536 lastResult = undefined
1537 }
1538
1539 // data is the last known good request result we have tracked - or if none has been tracked yet the last good result for the current args
1540 let data = currentState.isSuccess ? currentState.data : lastResult?.data
1541 if (data === undefined) data = currentState.data
1542
1543 const hasData = data !== undefined
1544
1545 // isFetching = true any time a request is in flight
1546 const isFetching = currentState.isLoading
1547
1548 // isLoading = true only when loading while no data is present yet (initial load with no data in the cache)
1549 const isLoading =
1550 (!lastResult || lastResult.isLoading || lastResult.isUninitialized) &&
1551 !hasData &&
1552 isFetching
1553
1554 // isSuccess = true when data is present and we're not refetching after an error.
1555 // That includes cases where the _current_ item is either actively
1556 // fetching or about to fetch due to an uninitialized entry.
1557 const isSuccess =
1558 currentState.isSuccess ||
1559 (hasData &&
1560 ((isFetching && !lastResult?.isError) || currentState.isUninitialized))
1561
1562 return {
1563 ...currentState,
1564 data,
1565 currentData: currentState.data,
1566 isFetching,
1567 isLoading,
1568 isSuccess,
1569 } as UseQueryStateDefaultResult<any>
1570 }
1571
1572 function infiniteQueryStatePreSelector(
1573 currentState: InfiniteQueryResultSelectorResult<any>,
1574 lastResult: UseInfiniteQueryStateDefaultResult<any> | undefined,
1575 queryArgs: any,
1576 ): UseInfiniteQueryStateDefaultResult<any> {
1577 // if we had a last result and the current result is uninitialized,
1578 // we might have called `api.util.resetApiState`
1579 // in this case, reset the hook
1580 if (lastResult?.endpointName && currentState.isUninitialized) {
1581 const { endpointName } = lastResult
1582 const endpointDefinition = endpointDefinitions[endpointName]
1583 if (
1584 queryArgs !== skipToken &&
1585 serializeQueryArgs({
1586 queryArgs: lastResult.originalArgs,
1587 endpointDefinition,
1588 endpointName,
1589 }) ===
1590 serializeQueryArgs({
1591 queryArgs,
1592 endpointDefinition,
1593 endpointName,
1594 })
1595 )
1596 lastResult = undefined
1597 }
1598
1599 // data is the last known good request result we have tracked - or if none has been tracked yet the last good result for the current args
1600 let data = currentState.isSuccess ? currentState.data : lastResult?.data
1601 if (data === undefined) data = currentState.data
1602
1603 const hasData = data !== undefined
1604
1605 // isFetching = true any time a request is in flight
1606 const isFetching = currentState.isLoading
1607 // isLoading = true only when loading while no data is present yet (initial load with no data in the cache)
1608 const isLoading =
1609 (!lastResult || lastResult.isLoading || lastResult.isUninitialized) &&
1610 !hasData &&
1611 isFetching
1612 // isSuccess = true when data is present
1613 const isSuccess = currentState.isSuccess || (isFetching && hasData)
1614
1615 return {
1616 ...currentState,
1617 data,
1618 currentData: currentState.data,
1619 isFetching,
1620 isLoading,
1621 isSuccess,
1622 } as UseInfiniteQueryStateDefaultResult<any>
1623 }
1624
1625 function usePrefetch<EndpointName extends QueryKeys<Definitions>>(
1626 endpointName: EndpointName,
1627 defaultOptions?: PrefetchOptions,
1628 ) {
1629 const dispatch = useDispatch<ThunkDispatch<any, any, UnknownAction>>()
1630 const stableDefaultOptions = useShallowStableValue(defaultOptions)
1631
1632 return useCallback(
1633 (arg: any, options?: PrefetchOptions) =>
1634 dispatch(
1635 (api.util.prefetch as GenericPrefetchThunk)(endpointName, arg, {
1636 ...stableDefaultOptions,
1637 ...options,
1638 }),
1639 ),
1640 [endpointName, dispatch, stableDefaultOptions],
1641 )
1642 }
1643
1644 function useQuerySubscriptionCommonImpl<
1645 T extends
1646 | QueryActionCreatorResult<any>
1647 | InfiniteQueryActionCreatorResult<any>,
1648 >(
1649 endpointName: string,
1650 arg: unknown | SkipToken,
1651 {
1652 refetchOnReconnect,
1653 refetchOnFocus,
1654 refetchOnMountOrArgChange,
1655 skip = false,
1656 pollingInterval = 0,
1657 skipPollingIfUnfocused = false,
1658 ...rest
1659 }: UseQuerySubscriptionOptions = {},
1660 ) {
1661 const { initiate } = api.endpoints[endpointName] as ApiEndpointQuery<
1662 QueryDefinition<any, any, any, any, any>,
1663 Definitions
1664 >
1665 const dispatch = useDispatch<ThunkDispatch<any, any, UnknownAction>>()
1666
1667 // TODO: Change this to `useRef<SubscriptionSelectors>(undefined)` after upgrading to React 19.
1668 const subscriptionSelectorsRef = useRef<SubscriptionSelectors | undefined>(
1669 undefined,
1670 )
1671
1672 if (!subscriptionSelectorsRef.current) {
1673 const returnedValue = dispatch(
1674 api.internalActions.internal_getRTKQSubscriptions(),
1675 )
1676
1677 if (process.env.NODE_ENV !== 'production') {
1678 if (
1679 typeof returnedValue !== 'object' ||
1680 typeof returnedValue?.type === 'string'
1681 ) {
1682 throw new Error(
1683 `Warning: Middleware for RTK-Query API at reducerPath "${api.reducerPath}" has not been added to the store.
1684 You must add the middleware for RTK-Query to function correctly!`,
1685 )
1686 }
1687 }
1688
1689 subscriptionSelectorsRef.current =
1690 returnedValue as unknown as SubscriptionSelectors
1691 }
1692 const stableArg = useStableQueryArgs(skip ? skipToken : arg)
1693 const stableSubscriptionOptions = useShallowStableValue({
1694 refetchOnReconnect,
1695 refetchOnFocus,
1696 pollingInterval,
1697 skipPollingIfUnfocused,
1698 })
1699
1700 const initialPageParam = (rest as UseInfiniteQuerySubscriptionOptions<any>)
1701 .initialPageParam
1702 const stableInitialPageParam = useShallowStableValue(initialPageParam)
1703
1704 const refetchCachedPages = (
1705 rest as UseInfiniteQuerySubscriptionOptions<any>
1706 ).refetchCachedPages
1707 const stableRefetchCachedPages = useShallowStableValue(refetchCachedPages)
1708
1709 /**
1710 * @todo Change this to `useRef<QueryActionCreatorResult<any>>(undefined)` after upgrading to React 19.
1711 */
1712 const promiseRef = useRef<T | undefined>(undefined)
1713
1714 let { queryCacheKey, requestId } = promiseRef.current || {}
1715
1716 // HACK We've saved the middleware subscription lookup callbacks into a ref,
1717 // so we can directly check here if the subscription exists for this query.
1718 let currentRenderHasSubscription = false
1719 if (queryCacheKey && requestId) {
1720 currentRenderHasSubscription =
1721 subscriptionSelectorsRef.current.isRequestSubscribed(
1722 queryCacheKey,
1723 requestId,
1724 )
1725 }
1726
1727 const subscriptionRemoved =
1728 !currentRenderHasSubscription && promiseRef.current !== undefined
1729
1730 usePossiblyImmediateEffect((): void | undefined => {
1731 if (subscriptionRemoved) {
1732 promiseRef.current = undefined
1733 }
1734 }, [subscriptionRemoved])
1735
1736 usePossiblyImmediateEffect((): void | undefined => {
1737 const lastPromise = promiseRef.current
1738 if (
1739 typeof process !== 'undefined' &&
1740 process.env.NODE_ENV === 'removeMeOnCompilation'
1741 ) {
1742 // this is only present to enforce the rule of hooks to keep `isSubscribed` in the dependency array
1743 console.log(subscriptionRemoved)
1744 }
1745
1746 if (stableArg === skipToken) {
1747 lastPromise?.unsubscribe()
1748 promiseRef.current = undefined
1749 return
1750 }
1751
1752 const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions
1753
1754 if (!lastPromise || lastPromise.arg !== stableArg) {
1755 lastPromise?.unsubscribe()
1756 const promise = dispatch(
1757 initiate(stableArg, {
1758 subscriptionOptions: stableSubscriptionOptions,
1759 forceRefetch: refetchOnMountOrArgChange,
1760 ...(isInfiniteQueryDefinition(endpointDefinitions[endpointName])
1761 ? {
1762 initialPageParam: stableInitialPageParam,
1763 refetchCachedPages: stableRefetchCachedPages,
1764 }
1765 : {}),
1766 }),
1767 )
1768
1769 promiseRef.current = promise as T
1770 } else if (stableSubscriptionOptions !== lastSubscriptionOptions) {
1771 lastPromise.updateSubscriptionOptions(stableSubscriptionOptions)
1772 }
1773 }, [
1774 dispatch,
1775 initiate,
1776 refetchOnMountOrArgChange,
1777 stableArg,
1778 stableSubscriptionOptions,
1779 subscriptionRemoved,
1780 stableInitialPageParam,
1781 stableRefetchCachedPages,
1782 endpointName,
1783 ])
1784
1785 return [promiseRef, dispatch, initiate, stableSubscriptionOptions] as const
1786 }
1787
1788 function buildUseQueryState(
1789 endpointName: string,
1790 preSelector:
1791 | typeof queryStatePreSelector
1792 | typeof infiniteQueryStatePreSelector,
1793 ) {
1794 const useQueryState = (
1795 arg: any,
1796 {
1797 skip = false,
1798 selectFromResult,
1799 }:
1800 | UseQueryStateOptions<any, any>
1801 | UseInfiniteQueryStateOptions<any, any> = {},
1802 ) => {
1803 const { select } = api.endpoints[endpointName] as ApiEndpointQuery<
1804 QueryDefinition<any, any, any, any, any>,
1805 Definitions
1806 >
1807 const stableArg = useStableQueryArgs(skip ? skipToken : arg)
1808
1809 type ApiRootState = Parameters<ReturnType<typeof select>>[0]
1810
1811 const lastValue = useRef<any>(undefined)
1812
1813 const selectDefaultResult: Selector<ApiRootState, any, [any]> = useMemo(
1814 () =>
1815 // Normally ts-ignores are bad and should be avoided, but we're
1816 // already casting this selector to be `Selector<any>` anyway,
1817 // so the inconsistencies don't matter here
1818 // @ts-ignore
1819 createSelector(
1820 [
1821 // @ts-ignore
1822 select(stableArg),
1823 (_: ApiRootState, lastResult: any) => lastResult,
1824 (_: ApiRootState) => stableArg,
1825 ],
1826 preSelector,
1827 {
1828 memoizeOptions: {
1829 resultEqualityCheck: shallowEqual,
1830 },
1831 },
1832 ),
1833 [select, stableArg],
1834 )
1835
1836 const querySelector: Selector<ApiRootState, any, [any]> = useMemo(
1837 () =>
1838 selectFromResult
1839 ? createSelector([selectDefaultResult], selectFromResult, {
1840 devModeChecks: { identityFunctionCheck: 'never' },
1841 })
1842 : selectDefaultResult,
1843 [selectDefaultResult, selectFromResult],
1844 )
1845
1846 const currentState = useSelector(
1847 (state: RootState<Definitions, any, any>) =>
1848 querySelector(state, lastValue.current),
1849 shallowEqual,
1850 )
1851
1852 const store = useStore<RootState<Definitions, any, any>>()
1853 const newLastValue = selectDefaultResult(
1854 store.getState(),
1855 lastValue.current,
1856 )
1857 useIsomorphicLayoutEffect(() => {
1858 lastValue.current = newLastValue
1859 }, [newLastValue])
1860
1861 return currentState
1862 }
1863
1864 return useQueryState
1865 }
1866
1867 function usePromiseRefUnsubscribeOnUnmount(
1868 promiseRef: UnsubscribePromiseRef,
1869 ) {
1870 useEffect(() => {
1871 return () => {
1872 unsubscribePromiseRef(promiseRef)
1873 // eslint-disable-next-line react-hooks/exhaustive-deps
1874 ;(promiseRef.current as any) = undefined
1875 }
1876 }, [promiseRef])
1877 }
1878
1879 function refetchOrErrorIfUnmounted<
1880 T extends
1881 | QueryActionCreatorResult<any>
1882 | InfiniteQueryActionCreatorResult<any>,
1883 >(promiseRef: React.RefObject<T | undefined>): T {
1884 if (!promiseRef.current)
1885 throw new Error('Cannot refetch a query that has not been started yet.')
1886 return promiseRef.current.refetch() as T
1887 }
1888
1889 function buildQueryHooks(endpointName: string): QueryHooks<any> {
1890 const useQuerySubscription: UseQuerySubscription<any> = (
1891 arg: any,
1892 options = {},
1893 ) => {
1894 const [promiseRef] = useQuerySubscriptionCommonImpl<
1895 QueryActionCreatorResult<any>
1896 >(endpointName, arg, options)
1897
1898 usePromiseRefUnsubscribeOnUnmount(promiseRef)
1899
1900 return useMemo(
1901 () => ({
1902 /**
1903 * A method to manually refetch data for the query
1904 */
1905 refetch: () => refetchOrErrorIfUnmounted(promiseRef),
1906 }),
1907 [promiseRef],
1908 )
1909 }
1910
1911 const useLazyQuerySubscription: UseLazyQuerySubscription<any> = ({
1912 refetchOnReconnect,
1913 refetchOnFocus,
1914 pollingInterval = 0,
1915 skipPollingIfUnfocused = false,
1916 } = {}) => {
1917 const { initiate } = api.endpoints[endpointName] as ApiEndpointQuery<
1918 QueryDefinition<any, any, any, any, any>,
1919 Definitions
1920 >
1921 const dispatch = useDispatch<ThunkDispatch<any, any, UnknownAction>>()
1922
1923 const [arg, setArg] = useState<any>(UNINITIALIZED_VALUE)
1924
1925 // TODO: Change this to `useRef<QueryActionCreatorResult<any>>(undefined)` after upgrading to React 19.
1926 /**
1927 * @todo Change this to `useRef<QueryActionCreatorResult<any>>(undefined)` after upgrading to React 19.
1928 */
1929 const promiseRef = useRef<QueryActionCreatorResult<any> | undefined>(
1930 undefined,
1931 )
1932
1933 const stableSubscriptionOptions = useShallowStableValue({
1934 refetchOnReconnect,
1935 refetchOnFocus,
1936 pollingInterval,
1937 skipPollingIfUnfocused,
1938 })
1939
1940 usePossiblyImmediateEffect(() => {
1941 const lastSubscriptionOptions = promiseRef.current?.subscriptionOptions
1942
1943 if (stableSubscriptionOptions !== lastSubscriptionOptions) {
1944 promiseRef.current?.updateSubscriptionOptions(
1945 stableSubscriptionOptions,
1946 )
1947 }
1948 }, [stableSubscriptionOptions])
1949
1950 const subscriptionOptionsRef = useRef(stableSubscriptionOptions)
1951 usePossiblyImmediateEffect(() => {
1952 subscriptionOptionsRef.current = stableSubscriptionOptions
1953 }, [stableSubscriptionOptions])
1954
1955 const trigger = useCallback(
1956 function (arg: any, preferCacheValue = false) {
1957 let promise: QueryActionCreatorResult<any>
1958
1959 batch(() => {
1960 unsubscribePromiseRef(promiseRef)
1961
1962 promiseRef.current = promise = dispatch(
1963 initiate(arg, {
1964 subscriptionOptions: subscriptionOptionsRef.current,
1965 forceRefetch: !preferCacheValue,
1966 }),
1967 )
1968
1969 setArg(arg)
1970 })
1971
1972 return promise!
1973 },
1974 [dispatch, initiate],
1975 )
1976
1977 const reset = useCallback(() => {
1978 if (promiseRef.current?.queryCacheKey) {
1979 dispatch(
1980 api.internalActions.removeQueryResult({
1981 queryCacheKey: promiseRef.current?.queryCacheKey as QueryCacheKey,
1982 }),
1983 )
1984 }
1985 }, [dispatch])
1986
1987 /* cleanup on unmount */
1988 useEffect(() => {
1989 return () => {
1990 unsubscribePromiseRef(promiseRef)
1991 }
1992 }, [])
1993
1994 /* if "cleanup on unmount" was triggered from a fast refresh, we want to reinstate the query */
1995 useEffect(() => {
1996 if (arg !== UNINITIALIZED_VALUE && !promiseRef.current) {
1997 trigger(arg, true)
1998 }
1999 }, [arg, trigger])
2000
2001 return useMemo(
2002 () => [trigger, arg, { reset }] as const,
2003 [trigger, arg, reset],
2004 )
2005 }
2006
2007 const useQueryState: UseQueryState<any> = buildUseQueryState(
2008 endpointName,
2009 queryStatePreSelector,
2010 )
2011
2012 return {
2013 useQueryState,
2014 useQuerySubscription,
2015 useLazyQuerySubscription,
2016 useLazyQuery(options) {
2017 const [trigger, arg, { reset }] = useLazyQuerySubscription(options)
2018 const queryStateResults = useQueryState(arg, {
2019 ...options,
2020 skip: arg === UNINITIALIZED_VALUE,
2021 })
2022
2023 const info = useMemo(() => ({ lastArg: arg }), [arg])
2024 return useMemo(
2025 () => [trigger, { ...queryStateResults, reset }, info],
2026 [trigger, queryStateResults, reset, info],
2027 )
2028 },
2029 useQuery(arg, options) {
2030 const querySubscriptionResults = useQuerySubscription(arg, options)
2031 const queryStateResults = useQueryState(arg, {
2032 selectFromResult:
2033 arg === skipToken || options?.skip
2034 ? undefined
2035 : noPendingQueryStateSelector,
2036 ...options,
2037 })
2038
2039 const debugValue = pick(queryStateResults, ...COMMON_HOOK_DEBUG_FIELDS)
2040 useDebugValue(debugValue)
2041
2042 return useMemo(
2043 () => ({ ...queryStateResults, ...querySubscriptionResults }),
2044 [queryStateResults, querySubscriptionResults],
2045 )
2046 },
2047 }
2048 }
2049
2050 function buildInfiniteQueryHooks(
2051 endpointName: string,
2052 ): InfiniteQueryHooks<any> {
2053 const useInfiniteQuerySubscription: UseInfiniteQuerySubscription<any> = (
2054 arg: any,
2055 options = {},
2056 ) => {
2057 const [promiseRef, dispatch, initiate, stableSubscriptionOptions] =
2058 useQuerySubscriptionCommonImpl<InfiniteQueryActionCreatorResult<any>>(
2059 endpointName,
2060 arg,
2061 options,
2062 )
2063
2064 const subscriptionOptionsRef = useRef(stableSubscriptionOptions)
2065 usePossiblyImmediateEffect(() => {
2066 subscriptionOptionsRef.current = stableSubscriptionOptions
2067 }, [stableSubscriptionOptions])
2068
2069 // Extract and stabilize the hook-level refetchCachedPages option
2070 const hookRefetchCachedPages = (
2071 options as UseInfiniteQuerySubscriptionOptions<any>
2072 ).refetchCachedPages
2073 const stableHookRefetchCachedPages = useShallowStableValue(
2074 hookRefetchCachedPages,
2075 )
2076
2077 const trigger: LazyInfiniteQueryTrigger<any> = useCallback(
2078 function (arg: unknown, direction: 'forward' | 'backward') {
2079 let promise: InfiniteQueryActionCreatorResult<any>
2080
2081 batch(() => {
2082 unsubscribePromiseRef(promiseRef)
2083
2084 promiseRef.current = promise = dispatch(
2085 (initiate as StartInfiniteQueryActionCreator<any>)(arg, {
2086 subscriptionOptions: subscriptionOptionsRef.current,
2087 direction,
2088 }),
2089 )
2090 })
2091
2092 return promise!
2093 },
2094 [promiseRef, dispatch, initiate],
2095 )
2096
2097 usePromiseRefUnsubscribeOnUnmount(promiseRef)
2098
2099 const stableArg = useStableQueryArgs(options.skip ? skipToken : arg)
2100
2101 const refetch = useCallback(
2102 (
2103 options?: Pick<
2104 UseInfiniteQuerySubscriptionOptions<any>,
2105 'refetchCachedPages'
2106 >,
2107 ) => {
2108 if (!promiseRef.current)
2109 throw new Error(
2110 'Cannot refetch a query that has not been started yet.',
2111 )
2112 // Merge per-call options with hook-level default
2113 const mergedOptions = {
2114 refetchCachedPages:
2115 options?.refetchCachedPages ?? stableHookRefetchCachedPages,
2116 }
2117 return promiseRef.current.refetch(mergedOptions)
2118 },
2119 [promiseRef, stableHookRefetchCachedPages],
2120 )
2121
2122 return useMemo(() => {
2123 const fetchNextPage = () => {
2124 return trigger(stableArg, 'forward')
2125 }
2126
2127 const fetchPreviousPage = () => {
2128 return trigger(stableArg, 'backward')
2129 }
2130
2131 return {
2132 trigger,
2133 /**
2134 * A method to manually refetch data for the query
2135 */
2136 refetch,
2137 fetchNextPage,
2138 fetchPreviousPage,
2139 }
2140 }, [refetch, trigger, stableArg])
2141 }
2142
2143 const useInfiniteQueryState: UseInfiniteQueryState<any> =
2144 buildUseQueryState(endpointName, infiniteQueryStatePreSelector)
2145
2146 return {
2147 useInfiniteQueryState,
2148 useInfiniteQuerySubscription,
2149 useInfiniteQuery(arg, options) {
2150 const { refetch, fetchNextPage, fetchPreviousPage } =
2151 useInfiniteQuerySubscription(arg, options)
2152 const queryStateResults = useInfiniteQueryState(arg, {
2153 selectFromResult:
2154 arg === skipToken || options?.skip
2155 ? undefined
2156 : noPendingQueryStateSelector,
2157 ...options,
2158 })
2159
2160 const debugValue = pick(
2161 queryStateResults,
2162 ...COMMON_HOOK_DEBUG_FIELDS,
2163 'hasNextPage',
2164 'hasPreviousPage',
2165 )
2166 useDebugValue(debugValue)
2167
2168 return useMemo(
2169 () => ({
2170 ...queryStateResults,
2171 fetchNextPage,
2172 fetchPreviousPage,
2173 refetch,
2174 }),
2175 [queryStateResults, fetchNextPage, fetchPreviousPage, refetch],
2176 )
2177 },
2178 }
2179 }
2180
2181 function buildMutationHook(name: string): UseMutation<any> {
2182 return ({ selectFromResult, fixedCacheKey } = {}) => {
2183 const { select, initiate } = api.endpoints[name] as ApiEndpointMutation<
2184 MutationDefinition<any, any, any, any, any>,
2185 Definitions
2186 >
2187 const dispatch = useDispatch<ThunkDispatch<any, any, UnknownAction>>()
2188 const [promise, setPromise] = useState<MutationActionCreatorResult<any>>()
2189
2190 useEffect(
2191 () => () => {
2192 if (!promise?.arg.fixedCacheKey) {
2193 promise?.reset()
2194 }
2195 },
2196 [promise],
2197 )
2198
2199 const triggerMutation = useCallback(
2200 function (arg: Parameters<typeof initiate>['0']) {
2201 const promise = dispatch(initiate(arg, { fixedCacheKey }))
2202 setPromise(promise)
2203 return promise
2204 },
2205 [dispatch, initiate, fixedCacheKey],
2206 )
2207
2208 const { requestId } = promise || {}
2209 const selectDefaultResult = useMemo(
2210 () => select({ fixedCacheKey, requestId: promise?.requestId }),
2211 [fixedCacheKey, promise, select],
2212 )
2213 const mutationSelector = useMemo(
2214 (): Selector<RootState<Definitions, any, any>, any> =>
2215 selectFromResult
2216 ? createSelector([selectDefaultResult], selectFromResult)
2217 : selectDefaultResult,
2218 [selectFromResult, selectDefaultResult],
2219 )
2220
2221 const currentState = useSelector(mutationSelector, shallowEqual)
2222 const originalArgs =
2223 fixedCacheKey == null ? promise?.arg.originalArgs : undefined
2224 const reset = useCallback(() => {
2225 batch(() => {
2226 if (promise) {
2227 setPromise(undefined)
2228 }
2229 if (fixedCacheKey) {
2230 dispatch(
2231 api.internalActions.removeMutationResult({
2232 requestId,
2233 fixedCacheKey,
2234 }),
2235 )
2236 }
2237 })
2238 }, [dispatch, fixedCacheKey, promise, requestId])
2239
2240 const debugValue = pick(
2241 currentState,
2242 ...COMMON_HOOK_DEBUG_FIELDS,
2243 'endpointName',
2244 )
2245 useDebugValue(debugValue)
2246
2247 const finalState = useMemo(
2248 () => ({ ...currentState, originalArgs, reset }),
2249 [currentState, originalArgs, reset],
2250 )
2251
2252 return useMemo(
2253 () => [triggerMutation, finalState] as const,
2254 [triggerMutation, finalState],
2255 )
2256 }
2257 }
2258}
Note: See TracBrowser for help on using the repository browser.