Skip to content

Commit 49748b9

Browse files
michal-kurzmarkerikson
authored andcommitted
feature : endpoint-specific query arg serialization
1 parent f768913 commit 49748b9

File tree

4 files changed

+117
-3
lines changed

4 files changed

+117
-3
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Dictionary } from '@reduxjs/toolkit'
2+
import {
3+
defaultSerializeQueryArgs,
4+
SerializeQueryArgs,
5+
} from './defaultSerializeQueryArgs'
6+
7+
export function buildSerializeQueryArgs(
8+
globalSerializer: SerializeQueryArgs<any> = defaultSerializeQueryArgs
9+
) {
10+
const endpointSpecificSerializers: Dictionary<SerializeQueryArgs<any>> = {}
11+
12+
const serializeQueryArgs: SerializeQueryArgs<any> = (params) => {
13+
const endpointSpecificSerializer =
14+
endpointSpecificSerializers[params.endpointName]
15+
16+
if (endpointSpecificSerializer) {
17+
return endpointSpecificSerializer(params)
18+
}
19+
20+
return globalSerializer(params)
21+
}
22+
23+
const registerArgsSerializerForEndpoint = (
24+
endpointName: string,
25+
serializer: SerializeQueryArgs<any>
26+
) => {
27+
endpointSpecificSerializers[endpointName] = serializer
28+
}
29+
30+
return {
31+
serializeQueryArgs,
32+
registerArgsSerializerForEndpoint,
33+
}
34+
}

packages/toolkit/src/query/createApi.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import type { Api, ApiContext, Module, ModuleName } from './apiTypes'
22
import type { CombinedState } from './core/apiState'
33
import type { BaseQueryArg, BaseQueryFn } from './baseQueryTypes'
44
import type { SerializeQueryArgs } from './defaultSerializeQueryArgs'
5-
import { defaultSerializeQueryArgs } from './defaultSerializeQueryArgs'
5+
import { buildSerializeQueryArgs } from './buildSerializeQueryArgs'
66
import type {
77
EndpointBuilder,
88
EndpointDefinitions,
99
} from './endpointDefinitions'
10-
import { DefinitionType } from './endpointDefinitions'
10+
import { DefinitionType, isQueryDefinition } from './endpointDefinitions'
1111
import { nanoid } from '@reduxjs/toolkit'
1212
import type { AnyAction } from '@reduxjs/toolkit'
1313
import type { NoInfer } from './tsHelpers'
@@ -236,15 +236,18 @@ export function buildCreateApi<Modules extends [Module<any>, ...Module<any>[]]>(
236236
})
237237
)
238238

239+
const { serializeQueryArgs, registerArgsSerializerForEndpoint } =
240+
buildSerializeQueryArgs(options.serializeQueryArgs)
241+
239242
const optionsWithDefaults = {
240243
reducerPath: 'api',
241-
serializeQueryArgs: defaultSerializeQueryArgs,
242244
keepUnusedDataFor: 60,
243245
refetchOnMountOrArgChange: false,
244246
refetchOnFocus: false,
245247
refetchOnReconnect: false,
246248
...options,
247249
extractRehydrationInfo,
250+
serializeQueryArgs,
248251
tagTypes: [...(options.tagTypes || [])],
249252
}
250253

@@ -319,6 +322,14 @@ export function buildCreateApi<Modules extends [Module<any>, ...Module<any>[]]>(
319322

320323
continue
321324
}
325+
326+
if (isQueryDefinition(definition) && definition.serializeQueryArgs) {
327+
registerArgsSerializerForEndpoint(
328+
endpointName,
329+
definition.serializeQueryArgs
330+
)
331+
}
332+
322333
context.endpointDefinitions[endpointName] = definition
323334
for (const m of initializedModules) {
324335
m.injectEndpoint(endpointName, definition)

packages/toolkit/src/query/endpointDefinitions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'
2+
import { SerializeQueryArgs } from './defaultSerializeQueryArgs'
23
import type { RootState } from './core/apiState'
34
import type {
45
BaseQueryExtraOptions,
@@ -272,6 +273,8 @@ export interface QueryExtraOptions<
272273
* Not to be used. A query should not invalidate tags in the cache.
273274
*/
274275
invalidatesTags?: never
276+
277+
serializeQueryArgs?: SerializeQueryArgs<any>
275278
}
276279

277280
export type QueryDefinition<

packages/toolkit/src/query/tests/createApi.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from './helpers'
1818
import { server } from './mocks/server'
1919
import { rest } from 'msw'
20+
import { SerializeQueryArgs } from '../defaultSerializeQueryArgs'
2021

2122
const originalEnv = process.env.NODE_ENV
2223
beforeAll(() => void ((process.env as any).NODE_ENV = 'development'))
@@ -818,3 +819,68 @@ describe('structuralSharing flag behaviors', () => {
818819
expect(firstRef.data === secondRef.data).toBeFalsy()
819820
})
820821
})
822+
823+
describe('custom serializeQueryArgs per endpoint', () => {
824+
const customArgsSerializer: SerializeQueryArgs<number> = ({
825+
endpointName,
826+
queryArgs,
827+
}) => `${endpointName}-${queryArgs}`
828+
829+
type SuccessResponse = { value: 'success' }
830+
831+
const serializer1 = jest.fn(customArgsSerializer)
832+
833+
const api = createApi({
834+
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
835+
endpoints: (build) => ({
836+
queryWithCustomSerializer: build.query<SuccessResponse, number>({
837+
query: () => ({ url: '/success' }),
838+
serializeQueryArgs: serializer1,
839+
}),
840+
}),
841+
})
842+
843+
const storeRef = setupApiStore(api)
844+
845+
it('Works via createApi', async () => {
846+
expect(serializer1).toHaveBeenCalledTimes(0)
847+
848+
await storeRef.store.dispatch(
849+
api.endpoints.queryWithCustomSerializer.initiate(5)
850+
)
851+
852+
const firstRef = api.endpoints.queryWithCustomSerializer.select(5)(
853+
storeRef.store.getState()
854+
)
855+
expect(serializer1).toHaveBeenCalled()
856+
857+
expect(firstRef.data).toEqual({ value: 'success' })
858+
})
859+
860+
const serializer2 = jest.fn(customArgsSerializer)
861+
862+
const injectedApi = api.injectEndpoints({
863+
endpoints: (build) => ({
864+
injectedQueryWithCustomSerializer: build.query<SuccessResponse, number>({
865+
query: () => ({ url: '/success' }),
866+
serializeQueryArgs: serializer2,
867+
}),
868+
}),
869+
})
870+
871+
it('Works via injectEndpoints', async () => {
872+
expect(serializer2).toHaveBeenCalledTimes(0)
873+
874+
await storeRef.store.dispatch(
875+
injectedApi.endpoints.injectedQueryWithCustomSerializer.initiate(5)
876+
)
877+
878+
const firstRef =
879+
injectedApi.endpoints.injectedQueryWithCustomSerializer.select(5)(
880+
storeRef.store.getState()
881+
)
882+
expect(serializer2).toHaveBeenCalled()
883+
884+
expect(firstRef.data).toEqual({ value: 'success' })
885+
})
886+
})

0 commit comments

Comments
 (0)