diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index c63c5a2b82..2b2daec350 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -1,13 +1,15 @@ import type { Api, ApiContext, Module, ModuleName } from './apiTypes' import type { CombinedState } from './core/apiState' import type { BaseQueryArg, BaseQueryFn } from './baseQueryTypes' -import type { SerializeQueryArgs } from './defaultSerializeQueryArgs' -import { defaultSerializeQueryArgs } from './defaultSerializeQueryArgs' +import { + defaultSerializeQueryArgs, + SerializeQueryArgs, +} from './defaultSerializeQueryArgs' import type { EndpointBuilder, EndpointDefinitions, } from './endpointDefinitions' -import { DefinitionType } from './endpointDefinitions' +import { DefinitionType, isQueryDefinition } from './endpointDefinitions' import { nanoid } from '@reduxjs/toolkit' import type { AnyAction } from '@reduxjs/toolkit' import type { NoInfer } from './tsHelpers' @@ -236,15 +238,25 @@ export function buildCreateApi, ...Module[]]>( }) ) - const optionsWithDefaults = { + const optionsWithDefaults: CreateApiOptions = { reducerPath: 'api', - serializeQueryArgs: defaultSerializeQueryArgs, keepUnusedDataFor: 60, refetchOnMountOrArgChange: false, refetchOnFocus: false, refetchOnReconnect: false, ...options, extractRehydrationInfo, + serializeQueryArgs(queryArgsApi) { + let finalSerializeQueryArgs = defaultSerializeQueryArgs + if ('serializeQueryArgs' in queryArgsApi.endpointDefinition) { + finalSerializeQueryArgs = + queryArgsApi.endpointDefinition.serializeQueryArgs! + } else if (options.serializeQueryArgs) { + finalSerializeQueryArgs = options.serializeQueryArgs + } + + return finalSerializeQueryArgs(queryArgsApi) + }, tagTypes: [...(options.tagTypes || [])], } @@ -266,8 +278,8 @@ export function buildCreateApi, ...Module[]]>( enhanceEndpoints({ addTagTypes, endpoints }) { if (addTagTypes) { for (const eT of addTagTypes) { - if (!optionsWithDefaults.tagTypes.includes(eT as any)) { - optionsWithDefaults.tagTypes.push(eT as any) + if (!optionsWithDefaults.tagTypes!.includes(eT as any)) { + ;(optionsWithDefaults.tagTypes as any[]).push(eT) } } } @@ -290,7 +302,7 @@ export function buildCreateApi, ...Module[]]>( } as Api const initializedModules = modules.map((m) => - m.init(api as any, optionsWithDefaults, context) + m.init(api as any, optionsWithDefaults as any, context) ) function injectEndpoints( @@ -319,6 +331,7 @@ export function buildCreateApi, ...Module[]]>( continue } + context.endpointDefinitions[endpointName] = definition for (const m of initializedModules) { m.injectEndpoint(endpointName, definition) diff --git a/packages/toolkit/src/query/endpointDefinitions.ts b/packages/toolkit/src/query/endpointDefinitions.ts index 44ce1a41b1..02293cc41c 100644 --- a/packages/toolkit/src/query/endpointDefinitions.ts +++ b/packages/toolkit/src/query/endpointDefinitions.ts @@ -1,4 +1,5 @@ import type { AnyAction, ThunkDispatch } from '@reduxjs/toolkit' +import { SerializeQueryArgs } from './defaultSerializeQueryArgs' import type { RootState } from './core/apiState' import type { BaseQueryExtraOptions, @@ -272,6 +273,8 @@ export interface QueryExtraOptions< * Not to be used. A query should not invalidate tags in the cache. */ invalidatesTags?: never + + serializeQueryArgs?: SerializeQueryArgs } export type QueryDefinition< diff --git a/packages/toolkit/src/query/tests/createApi.test.ts b/packages/toolkit/src/query/tests/createApi.test.ts index fbf729841a..0fbfcb9a75 100644 --- a/packages/toolkit/src/query/tests/createApi.test.ts +++ b/packages/toolkit/src/query/tests/createApi.test.ts @@ -17,6 +17,7 @@ import { } from './helpers' import { server } from './mocks/server' import { rest } from 'msw' +import { SerializeQueryArgs } from '../defaultSerializeQueryArgs' const originalEnv = process.env.NODE_ENV beforeAll(() => void ((process.env as any).NODE_ENV = 'development')) @@ -818,3 +819,79 @@ describe('structuralSharing flag behaviors', () => { expect(firstRef.data === secondRef.data).toBeFalsy() }) }) + +describe('custom serializeQueryArgs per endpoint', () => { + const customArgsSerializer: SerializeQueryArgs = ({ + endpointName, + queryArgs, + }) => `${endpointName}-${queryArgs}` + + type SuccessResponse = { value: 'success' } + + const serializer1 = jest.fn(customArgsSerializer) + + const api = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }), + serializeQueryArgs: ({ endpointName, queryArgs }) => + `base-${endpointName}-${queryArgs}`, + endpoints: (build) => ({ + queryWithNoSerializer: build.query({ + query: (arg) => `${arg}`, + }), + queryWithCustomSerializer: build.query({ + query: (arg) => `${arg}`, + serializeQueryArgs: serializer1, + }), + }), + }) + + const storeRef = setupApiStore(api) + + it('Works via createApi', async () => { + await storeRef.store.dispatch( + api.endpoints.queryWithNoSerializer.initiate(99) + ) + + expect(serializer1).toHaveBeenCalledTimes(0) + + await storeRef.store.dispatch( + api.endpoints.queryWithCustomSerializer.initiate(42) + ) + + expect(serializer1).toHaveBeenCalled() + + expect( + storeRef.store.getState().api.queries['base-queryWithNoSerializer-99'] + ).toBeTruthy() + + expect( + storeRef.store.getState().api.queries['queryWithCustomSerializer-42'] + ).toBeTruthy() + }) + + const serializer2 = jest.fn(customArgsSerializer) + + const injectedApi = api.injectEndpoints({ + endpoints: (build) => ({ + injectedQueryWithCustomSerializer: build.query({ + query: (arg) => `${arg}`, + serializeQueryArgs: serializer2, + }), + }), + }) + + it('Works via injectEndpoints', async () => { + expect(serializer2).toHaveBeenCalledTimes(0) + + await storeRef.store.dispatch( + injectedApi.endpoints.injectedQueryWithCustomSerializer.initiate(5) + ) + + expect(serializer2).toHaveBeenCalled() + expect( + storeRef.store.getState().api.queries[ + 'injectedQueryWithCustomSerializer-5' + ] + ).toBeTruthy() + }) +})