From d9a9106c204abd60ffa4969a638d3afa33050f25 Mon Sep 17 00:00:00 2001 From: Andrej Pavlovic Date: Wed, 18 Sep 2024 22:40:30 -0400 Subject: [PATCH 01/11] Timeout triggered on `createApi` endpoint now correctly returns `TIMEOUT_ERROR` instead of generic `AbortError`. --- packages/toolkit/src/query/fetchBaseQuery.ts | 15 ++++++-- .../toolkit/src/query/tests/createApi.test.ts | 35 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/query/fetchBaseQuery.ts b/packages/toolkit/src/query/fetchBaseQuery.ts index 00cf6e54ab..9dc14d9790 100644 --- a/packages/toolkit/src/query/fetchBaseQuery.ts +++ b/packages/toolkit/src/query/fetchBaseQuery.ts @@ -213,7 +213,7 @@ export function fetchBaseQuery({ ) } return async (arg, api) => { - const { signal, getState, extra, endpoint, forced, type } = api + const { getState, extra, endpoint, forced, type } = api let meta: FetchBaseQueryMeta | undefined let { url, @@ -224,6 +224,14 @@ export function fetchBaseQuery({ timeout = defaultTimeout, ...rest } = typeof arg == 'string' ? { url: arg } : arg + + let abortController: AbortController | undefined, signal = api.signal + if (timeout) { + abortController = new AbortController() + api.signal.addEventListener('abort', abortController.abort) + signal = abortController.signal + } + let config: RequestInit = { ...baseFetchOptions, signal, @@ -272,10 +280,10 @@ export function fetchBaseQuery({ let response, timedOut = false, timeoutId = - timeout && + abortController && setTimeout(() => { timedOut = true - api.abort() + abortController!.abort() }, timeout) try { response = await fetchFn(request) @@ -289,6 +297,7 @@ export function fetchBaseQuery({ } } finally { if (timeoutId) clearTimeout(timeoutId) + abortController?.signal.removeEventListener('abort', abortController.abort) } const responseClone = response.clone() diff --git a/packages/toolkit/src/query/tests/createApi.test.ts b/packages/toolkit/src/query/tests/createApi.test.ts index ae745b18b2..9c97dc6465 100644 --- a/packages/toolkit/src/query/tests/createApi.test.ts +++ b/packages/toolkit/src/query/tests/createApi.test.ts @@ -1128,3 +1128,38 @@ describe('custom serializeQueryArgs per endpoint', () => { }) }) }) + +describe('timeout behavior', () => { + test('triggers TIMEOUT_ERROR', async () => { + const api = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com', timeout: 5 }), + endpoints: (build) => ({ + query: build.query({ + query: () => '/success', + }), + }), + }) + + const storeRef = setupApiStore(api, undefined, { + withoutTestLifecycles: true, + }) + + server.use( + http.get( + 'https://example.com/success', + async () => { + await delay(10) + return HttpResponse.json({ value: 'failed' }, { status: 500 }) + }, + { once: true }, + ), + ) + + const result = await storeRef.store.dispatch(api.endpoints.query.initiate()) + + expect(result?.error).toEqual({ + status: 'TIMEOUT_ERROR', + error: expect.stringMatching(/^AbortError:/), + }) + }) +}) From a877759cdc70f6de382754956dbdc90b36e6ab5e Mon Sep 17 00:00:00 2001 From: HaakonSvane Date: Fri, 20 Sep 2024 10:00:49 +0200 Subject: [PATCH 02/11] expose queryCacheKey in in baseQuery. write tests --- packages/toolkit/src/query/baseQueryTypes.ts | 4 ++ .../toolkit/src/query/core/buildThunks.ts | 1 + .../src/query/tests/buildInitiate.test.tsx | 56 ++++++++++++++++++- .../toolkit/src/query/tests/createApi.test.ts | 23 +++++++- 4 files changed, 81 insertions(+), 3 deletions(-) diff --git a/packages/toolkit/src/query/baseQueryTypes.ts b/packages/toolkit/src/query/baseQueryTypes.ts index 9af22a3f7e..0553426bf1 100644 --- a/packages/toolkit/src/query/baseQueryTypes.ts +++ b/packages/toolkit/src/query/baseQueryTypes.ts @@ -18,6 +18,10 @@ export interface BaseQueryApi { * invalidated queries. */ forced?: boolean + /** + * Only available for queries: the cache key that was used to store the query result + */ + queryCacheKey?: string } export type QueryReturnValue = diff --git a/packages/toolkit/src/query/core/buildThunks.ts b/packages/toolkit/src/query/core/buildThunks.ts index 740a11404a..8d9b69a2f9 100644 --- a/packages/toolkit/src/query/core/buildThunks.ts +++ b/packages/toolkit/src/query/core/buildThunks.ts @@ -381,6 +381,7 @@ export function buildThunks< type: arg.type, forced: arg.type === 'query' ? isForcedQuery(arg, getState()) : undefined, + queryCacheKey: arg.type === 'query' ? arg.queryCacheKey : undefined, } const forceQueryFn = diff --git a/packages/toolkit/src/query/tests/buildInitiate.test.tsx b/packages/toolkit/src/query/tests/buildInitiate.test.tsx index f7022acf30..08be5a3558 100644 --- a/packages/toolkit/src/query/tests/buildInitiate.test.tsx +++ b/packages/toolkit/src/query/tests/buildInitiate.test.tsx @@ -1,4 +1,4 @@ -import { setupApiStore } from '../../tests/utils/helpers' +import { setupApiStore } from '@internal/tests/utils/helpers' import { createApi } from '../core' import type { SubscriptionSelectors } from '../core/buildMiddleware/types' import { fakeBaseQuery } from '../fakeBaseQuery' @@ -119,3 +119,57 @@ describe('calling initiate without a cache entry, with subscribe: false still re ).toBe(false) }) }) + +describe('calling initiate should have resulting queryCacheKey match baseQuery queryCacheKey', () => { + const baseQuery = vi.fn(() => ({ data: 'success' })) + function getNewApi() { + return createApi({ + baseQuery, + endpoints: (build) => ({ + query: build.query({ + query: (args) => `queryUrl/${args.arg1}/${args.arg2}`, + }), + mutation: build.mutation({ + query: () => 'mutationUrl', + }), + }), + }) + } + let api = getNewApi() + beforeEach(() => { + baseQuery.mockClear() + api = getNewApi() + }) + + test('should be a string and matching on queries', () => { + const { store: storeApi } = setupApiStore(api, undefined, { + withoutTestLifecycles: true, + }) + const promise = storeApi.dispatch( + api.endpoints.query.initiate({ arg2: 'secondArg', arg1: 'firstArg' }), + ) + expect(baseQuery).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + queryCacheKey: promise.queryCacheKey, + }), + undefined, + ) + }) + + test('should be undefined and matching on mutations', () => { + const { store: storeApi } = setupApiStore(api, undefined, { + withoutTestLifecycles: true, + }) + storeApi.dispatch( + api.endpoints.mutation.initiate({ arg2: 'secondArg', arg1: 'firstArg' }), + ) + expect(baseQuery).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + queryCacheKey: undefined, + }), + undefined, + ) + }) +}) diff --git a/packages/toolkit/src/query/tests/createApi.test.ts b/packages/toolkit/src/query/tests/createApi.test.ts index ae745b18b2..7c277b4618 100644 --- a/packages/toolkit/src/query/tests/createApi.test.ts +++ b/packages/toolkit/src/query/tests/createApi.test.ts @@ -314,6 +314,7 @@ describe('endpoint definition typings', () => { getState: expect.any(Function), signal: expect.any(Object), type: expect.any(String), + queryCacheKey: expect.any(String), } beforeEach(() => { baseQuery.mockClear() @@ -355,6 +356,7 @@ describe('endpoint definition typings', () => { abort: expect.any(Function), forced: expect.any(Boolean), type: expect.any(String), + queryCacheKey: expect.any(String), }, undefined, ], @@ -368,6 +370,7 @@ describe('endpoint definition typings', () => { abort: expect.any(Function), forced: expect.any(Boolean), type: expect.any(String), + queryCacheKey: expect.any(String), }, undefined, ], @@ -499,8 +502,24 @@ describe('endpoint definition typings', () => { expect(baseQuery.mock.calls).toEqual([ ['modified1', commonBaseQueryApi, undefined], ['modified2', commonBaseQueryApi, undefined], - ['modified1', { ...commonBaseQueryApi, forced: undefined }, undefined], - ['modified2', { ...commonBaseQueryApi, forced: undefined }, undefined], + [ + 'modified1', + { + ...commonBaseQueryApi, + forced: undefined, + queryCacheKey: undefined, + }, + undefined, + ], + [ + 'modified2', + { + ...commonBaseQueryApi, + forced: undefined, + queryCacheKey: undefined, + }, + undefined, + ], ]) }) From 8eb2ef6102531cde3ec661c6a8dc9f478e58ab89 Mon Sep 17 00:00:00 2001 From: HaakonSvane Date: Fri, 20 Sep 2024 10:19:20 +0200 Subject: [PATCH 03/11] add entry for queryCacheKey in createApi docs --- docs/rtk-query/api/createApi.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/rtk-query/api/createApi.mdx b/docs/rtk-query/api/createApi.mdx index 24b205556d..615e0cc23d 100644 --- a/docs/rtk-query/api/createApi.mdx +++ b/docs/rtk-query/api/createApi.mdx @@ -91,6 +91,7 @@ export const { useGetPokemonByNameQuery } = pokemonApi - `endpoint` - The name of the endpoint. - `type` - Type of request (`query` or `mutation`). - `forced` - Indicates if a query has been forced. + - `queryCacheKey`- The computed query cache key - `extraOptions` - The value of the optional `extraOptions` property provided for a given endpoint #### baseQuery function signature From 8b510b4415dba952b6dbcd2e725b2cb2ac9371ed Mon Sep 17 00:00:00 2001 From: HaakonSvane Date: Fri, 20 Sep 2024 10:20:46 +0200 Subject: [PATCH 04/11] add missing period in docs --- docs/rtk-query/api/createApi.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rtk-query/api/createApi.mdx b/docs/rtk-query/api/createApi.mdx index 615e0cc23d..d1f48588db 100644 --- a/docs/rtk-query/api/createApi.mdx +++ b/docs/rtk-query/api/createApi.mdx @@ -91,7 +91,7 @@ export const { useGetPokemonByNameQuery } = pokemonApi - `endpoint` - The name of the endpoint. - `type` - Type of request (`query` or `mutation`). - `forced` - Indicates if a query has been forced. - - `queryCacheKey`- The computed query cache key + - `queryCacheKey`- The computed query cache key. - `extraOptions` - The value of the optional `extraOptions` property provided for a given endpoint #### baseQuery function signature From 7c0d93a19237f0a12c68804992a1058aac701061 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 4 Sep 2024 01:07:03 -0500 Subject: [PATCH 05/11] Mark the `SubscriptionOptions` type with an `@public` JSDoc tag --- packages/toolkit/src/query/core/apiState.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/toolkit/src/query/core/apiState.ts b/packages/toolkit/src/query/core/apiState.ts index cdf6831d2b..4a2a664ab4 100644 --- a/packages/toolkit/src/query/core/apiState.ts +++ b/packages/toolkit/src/query/core/apiState.ts @@ -78,6 +78,9 @@ export function getRequestStatusFlags(status: QueryStatus): RequestStatusFlags { } as any } +/** + * @public + */ export type SubscriptionOptions = { /** * How frequently to automatically re-fetch data (in milliseconds). Defaults to `0` (off). From e4791ea3827b7da076bd27e187aed2cb1d4bba14 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 11 Sep 2024 15:23:06 -0500 Subject: [PATCH 06/11] Mark `UseQueryStateOptions` with the `@internal` JSDoc tag --- packages/toolkit/src/query/react/buildHooks.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index aee59a3d9e..f3855b7bd0 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -357,6 +357,9 @@ export type TypedUseQueryState< QueryDefinition > +/** + * @internal + */ export type UseQueryStateOptions< D extends QueryDefinition, R extends Record, From 93bb3696d8f86f162d5565fefe42fc151934e330 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 11 Sep 2024 15:33:08 -0500 Subject: [PATCH 07/11] Add `TypedUseQueryStateOptions` helper type --- .../toolkit/src/query/react/buildHooks.ts | 24 +++++++++++++++++++ packages/toolkit/src/query/react/index.ts | 1 + 2 files changed, 25 insertions(+) diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index f3855b7bd0..4197e41cdc 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -430,6 +430,30 @@ export type UseQueryStateOptions< selectFromResult?: QueryStateSelector } +/** + * Allows you to define a "pre-typed" version of + * {@linkcode UseQueryStateOptions} for a specific query. + * + * @template ResultType - The type of the data returned by the query. + * @template QueryArg - The type of the argument passed to the query. + * @template BaseQuery - The type of the base query function used by the query. + * @template SelectedResult - The type of the selected result returned by __`selectFromResult`__. + * + * @since 2.7.8 + * @public + */ +export type TypedUseQueryStateOptions< + ResultType, + QueryArg, + BaseQuery extends BaseQueryFn, + SelectedResult extends Record = UseQueryStateDefaultResult< + QueryDefinition + >, +> = UseQueryStateOptions< + QueryDefinition, + SelectedResult +> + export type UseQueryStateResult< _ extends QueryDefinition, R, diff --git a/packages/toolkit/src/query/react/index.ts b/packages/toolkit/src/query/react/index.ts index 9708412874..8f4ae07f84 100644 --- a/packages/toolkit/src/query/react/index.ts +++ b/packages/toolkit/src/query/react/index.ts @@ -26,6 +26,7 @@ export type { TypedUseQuery, TypedUseQuerySubscription, TypedUseLazyQuerySubscription, + TypedUseQueryStateOptions, } from './buildHooks' export { UNINITIALIZED_VALUE } from './constants' export { createApi, reactHooksModule, reactHooksModuleName } From de2aa1d3cab55ea156a21bbd01e1564d5a23227a Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 11 Sep 2024 15:33:29 -0500 Subject: [PATCH 08/11] Add type tests for `TypedUseQueryStateOptions` --- .../src/query/tests/unionTypes.test-d.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/toolkit/src/query/tests/unionTypes.test-d.ts b/packages/toolkit/src/query/tests/unionTypes.test-d.ts index 1c5768d24a..6426556428 100644 --- a/packages/toolkit/src/query/tests/unionTypes.test-d.ts +++ b/packages/toolkit/src/query/tests/unionTypes.test-d.ts @@ -1,6 +1,8 @@ +import type { UseQueryStateOptions } from '@internal/query/react/buildHooks' import type { SerializedError } from '@reduxjs/toolkit' import type { FetchBaseQueryError, + QueryDefinition, TypedUseMutationResult, TypedUseQueryHookResult, TypedUseQueryState, @@ -13,6 +15,7 @@ import type { TypedMutationTrigger, TypedUseQuerySubscription, TypedUseQuery, + TypedUseQueryStateOptions, } from '@reduxjs/toolkit/query/react' import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' @@ -776,6 +779,23 @@ describe('"Typed" helper types', () => { >().toEqualTypeOf(result) }) + test('useQueryState options', () => { + expectTypeOf< + TypedUseQueryStateOptions + >().toMatchTypeOf< + Parameters[1] + >() + + expectTypeOf< + UseQueryStateOptions< + QueryDefinition, + { x: boolean } + > + >().toEqualTypeOf< + TypedUseQueryStateOptions + >() + }) + test('useQuerySubscription', () => { expectTypeOf< TypedUseQuerySubscription From 129ba46d4b5cd61aca4f87c2c700858623e97a43 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 11 Sep 2024 16:30:32 -0500 Subject: [PATCH 09/11] Improve on JSDocs of `TypedUseQueryStateOptions` --- packages/toolkit/src/query/react/buildHooks.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 4197e41cdc..26870d4ea2 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -431,13 +431,16 @@ export type UseQueryStateOptions< } /** - * Allows you to define a "pre-typed" version of - * {@linkcode UseQueryStateOptions} for a specific query. + * Provides a way to define a "pre-typed" version of + * {@linkcode UseQueryStateOptions} with specific options for a given query. + * This is particularly useful for setting default query behaviors such as + * refetching strategies, which can be overridden as needed. * - * @template ResultType - The type of the data returned by the query. - * @template QueryArg - The type of the argument passed to the query. - * @template BaseQuery - The type of the base query function used by the query. - * @template SelectedResult - The type of the selected result returned by __`selectFromResult`__. + * + * @template ResultType - The type of the result `data` returned by the query. + * @template QueryArg - The type of the argument passed into the query. + * @template BaseQuery - The type of the base query function being used. + * @template SelectedResult - The type of the selected result returned by the __`selectFromResult`__ function. * * @since 2.7.8 * @public From e56997103895447a4931601fa8b61368824482f2 Mon Sep 17 00:00:00 2001 From: Arya Emami Date: Wed, 11 Sep 2024 16:30:49 -0500 Subject: [PATCH 10/11] Add example for `TypedUseQueryStateOptions` --- .../toolkit/src/query/react/buildHooks.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 26870d4ea2..879851a533 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -436,6 +436,52 @@ export type UseQueryStateOptions< * This is particularly useful for setting default query behaviors such as * refetching strategies, which can be overridden as needed. * + * @example + * #### __Create a `useQuery` hook with default options__ + * + * ```ts + * import type { + * SubscriptionOptions, + * TypedUseQueryStateOptions, + * } from '@reduxjs/toolkit/query/react' + * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + * + * type Post = { + * id: number + * name: string + * } + * + * const api = createApi({ + * baseQuery: fetchBaseQuery({ baseUrl: '/' }), + * tagTypes: ['Post'], + * endpoints: (build) => ({ + * getPosts: build.query({ + * query: () => 'posts', + * }), + * }), + * }) + * + * const { useGetPostsQuery } = api + * + * export const useGetPostsQueryWithDefaults = < + * SelectedResult extends Record, + * >( + * overrideOptions: TypedUseQueryStateOptions< + * Post[], + * void, + * ReturnType, + * SelectedResult + * > & + * SubscriptionOptions, + * ) => + * useGetPostsQuery(undefined, { + * // Insert default options here + * + * refetchOnMountOrArgChange: true, + * refetchOnFocus: true, + * ...overrideOptions, + * }) + * ``` * * @template ResultType - The type of the result `data` returned by the query. * @template QueryArg - The type of the argument passed into the query. From 551ad03882ac936fdce8755bcfd34372441d2fd2 Mon Sep 17 00:00:00 2001 From: hornta Date: Tue, 24 Sep 2024 02:12:32 +0200 Subject: [PATCH 11/11] type: export QueryReturnValue --- packages/toolkit/src/query/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/toolkit/src/query/index.ts b/packages/toolkit/src/query/index.ts index 6203d60d37..511ea76eaa 100644 --- a/packages/toolkit/src/query/index.ts +++ b/packages/toolkit/src/query/index.ts @@ -17,6 +17,7 @@ export type { BaseQueryApi, BaseQueryEnhancer, BaseQueryFn, + QueryReturnValue } from './baseQueryTypes' export type { BaseEndpointDefinition,