From 49748b9915b0e9a442f91210c4dc1f360188a803 Mon Sep 17 00:00:00 2001 From: Michal Kurz Date: Thu, 7 Jul 2022 00:48:15 +0200 Subject: [PATCH 1/2] feature : endpoint-specific query arg serialization --- .../src/query/buildSerializeQueryArgs.ts | 34 ++++++++++ packages/toolkit/src/query/createApi.ts | 17 ++++- .../toolkit/src/query/endpointDefinitions.ts | 3 + .../toolkit/src/query/tests/createApi.test.ts | 66 +++++++++++++++++++ 4 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 packages/toolkit/src/query/buildSerializeQueryArgs.ts diff --git a/packages/toolkit/src/query/buildSerializeQueryArgs.ts b/packages/toolkit/src/query/buildSerializeQueryArgs.ts new file mode 100644 index 0000000000..6f924e5330 --- /dev/null +++ b/packages/toolkit/src/query/buildSerializeQueryArgs.ts @@ -0,0 +1,34 @@ +import { Dictionary } from '@reduxjs/toolkit' +import { + defaultSerializeQueryArgs, + SerializeQueryArgs, +} from './defaultSerializeQueryArgs' + +export function buildSerializeQueryArgs( + globalSerializer: SerializeQueryArgs = defaultSerializeQueryArgs +) { + const endpointSpecificSerializers: Dictionary> = {} + + const serializeQueryArgs: SerializeQueryArgs = (params) => { + const endpointSpecificSerializer = + endpointSpecificSerializers[params.endpointName] + + if (endpointSpecificSerializer) { + return endpointSpecificSerializer(params) + } + + return globalSerializer(params) + } + + const registerArgsSerializerForEndpoint = ( + endpointName: string, + serializer: SerializeQueryArgs + ) => { + endpointSpecificSerializers[endpointName] = serializer + } + + return { + serializeQueryArgs, + registerArgsSerializerForEndpoint, + } +} diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index c63c5a2b82..ceee632636 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -2,12 +2,12 @@ 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 { buildSerializeQueryArgs } from './buildSerializeQueryArgs' 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 +236,18 @@ export function buildCreateApi, ...Module[]]>( }) ) + const { serializeQueryArgs, registerArgsSerializerForEndpoint } = + buildSerializeQueryArgs(options.serializeQueryArgs) + const optionsWithDefaults = { reducerPath: 'api', - serializeQueryArgs: defaultSerializeQueryArgs, keepUnusedDataFor: 60, refetchOnMountOrArgChange: false, refetchOnFocus: false, refetchOnReconnect: false, ...options, extractRehydrationInfo, + serializeQueryArgs, tagTypes: [...(options.tagTypes || [])], } @@ -319,6 +322,14 @@ export function buildCreateApi, ...Module[]]>( continue } + + if (isQueryDefinition(definition) && definition.serializeQueryArgs) { + registerArgsSerializerForEndpoint( + endpointName, + definition.serializeQueryArgs + ) + } + 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..04ae6290a5 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,68 @@ 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' }), + endpoints: (build) => ({ + queryWithCustomSerializer: build.query({ + query: () => ({ url: '/success' }), + serializeQueryArgs: serializer1, + }), + }), + }) + + const storeRef = setupApiStore(api) + + it('Works via createApi', async () => { + expect(serializer1).toHaveBeenCalledTimes(0) + + await storeRef.store.dispatch( + api.endpoints.queryWithCustomSerializer.initiate(5) + ) + + const firstRef = api.endpoints.queryWithCustomSerializer.select(5)( + storeRef.store.getState() + ) + expect(serializer1).toHaveBeenCalled() + + expect(firstRef.data).toEqual({ value: 'success' }) + }) + + const serializer2 = jest.fn(customArgsSerializer) + + const injectedApi = api.injectEndpoints({ + endpoints: (build) => ({ + injectedQueryWithCustomSerializer: build.query({ + query: () => ({ url: '/success' }), + serializeQueryArgs: serializer2, + }), + }), + }) + + it('Works via injectEndpoints', async () => { + expect(serializer2).toHaveBeenCalledTimes(0) + + await storeRef.store.dispatch( + injectedApi.endpoints.injectedQueryWithCustomSerializer.initiate(5) + ) + + const firstRef = + injectedApi.endpoints.injectedQueryWithCustomSerializer.select(5)( + storeRef.store.getState() + ) + expect(serializer2).toHaveBeenCalled() + + expect(firstRef.data).toEqual({ value: 'success' }) + }) +}) From e4c67658a387d99dca1fa3d3ee0f73271fcfe3a8 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Wed, 10 Aug 2022 22:32:30 -0400 Subject: [PATCH 2/2] Consolidate serializeQueryArgs override behavior into createApi --- .../src/query/buildSerializeQueryArgs.ts | 34 ----------------- packages/toolkit/src/query/createApi.ts | 36 +++++++++--------- .../toolkit/src/query/tests/createApi.test.ts | 37 ++++++++++++------- 3 files changed, 43 insertions(+), 64 deletions(-) delete mode 100644 packages/toolkit/src/query/buildSerializeQueryArgs.ts diff --git a/packages/toolkit/src/query/buildSerializeQueryArgs.ts b/packages/toolkit/src/query/buildSerializeQueryArgs.ts deleted file mode 100644 index 6f924e5330..0000000000 --- a/packages/toolkit/src/query/buildSerializeQueryArgs.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Dictionary } from '@reduxjs/toolkit' -import { - defaultSerializeQueryArgs, - SerializeQueryArgs, -} from './defaultSerializeQueryArgs' - -export function buildSerializeQueryArgs( - globalSerializer: SerializeQueryArgs = defaultSerializeQueryArgs -) { - const endpointSpecificSerializers: Dictionary> = {} - - const serializeQueryArgs: SerializeQueryArgs = (params) => { - const endpointSpecificSerializer = - endpointSpecificSerializers[params.endpointName] - - if (endpointSpecificSerializer) { - return endpointSpecificSerializer(params) - } - - return globalSerializer(params) - } - - const registerArgsSerializerForEndpoint = ( - endpointName: string, - serializer: SerializeQueryArgs - ) => { - endpointSpecificSerializers[endpointName] = serializer - } - - return { - serializeQueryArgs, - registerArgsSerializerForEndpoint, - } -} diff --git a/packages/toolkit/src/query/createApi.ts b/packages/toolkit/src/query/createApi.ts index ceee632636..2b2daec350 100644 --- a/packages/toolkit/src/query/createApi.ts +++ b/packages/toolkit/src/query/createApi.ts @@ -1,8 +1,10 @@ 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 { buildSerializeQueryArgs } from './buildSerializeQueryArgs' +import { + defaultSerializeQueryArgs, + SerializeQueryArgs, +} from './defaultSerializeQueryArgs' import type { EndpointBuilder, EndpointDefinitions, @@ -236,10 +238,7 @@ export function buildCreateApi, ...Module[]]>( }) ) - const { serializeQueryArgs, registerArgsSerializerForEndpoint } = - buildSerializeQueryArgs(options.serializeQueryArgs) - - const optionsWithDefaults = { + const optionsWithDefaults: CreateApiOptions = { reducerPath: 'api', keepUnusedDataFor: 60, refetchOnMountOrArgChange: false, @@ -247,7 +246,17 @@ export function buildCreateApi, ...Module[]]>( refetchOnReconnect: false, ...options, extractRehydrationInfo, - serializeQueryArgs, + 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 || [])], } @@ -269,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) } } } @@ -293,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( @@ -323,13 +332,6 @@ export function buildCreateApi, ...Module[]]>( continue } - if (isQueryDefinition(definition) && definition.serializeQueryArgs) { - registerArgsSerializerForEndpoint( - endpointName, - definition.serializeQueryArgs - ) - } - context.endpointDefinitions[endpointName] = definition for (const m of initializedModules) { m.injectEndpoint(endpointName, definition) diff --git a/packages/toolkit/src/query/tests/createApi.test.ts b/packages/toolkit/src/query/tests/createApi.test.ts index 04ae6290a5..0fbfcb9a75 100644 --- a/packages/toolkit/src/query/tests/createApi.test.ts +++ b/packages/toolkit/src/query/tests/createApi.test.ts @@ -832,9 +832,14 @@ describe('custom serializeQueryArgs per endpoint', () => { 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: () => ({ url: '/success' }), + query: (arg) => `${arg}`, serializeQueryArgs: serializer1, }), }), @@ -843,18 +848,25 @@ describe('custom serializeQueryArgs per endpoint', () => { 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(5) + api.endpoints.queryWithCustomSerializer.initiate(42) ) - const firstRef = api.endpoints.queryWithCustomSerializer.select(5)( - storeRef.store.getState() - ) expect(serializer1).toHaveBeenCalled() - expect(firstRef.data).toEqual({ value: 'success' }) + expect( + storeRef.store.getState().api.queries['base-queryWithNoSerializer-99'] + ).toBeTruthy() + + expect( + storeRef.store.getState().api.queries['queryWithCustomSerializer-42'] + ).toBeTruthy() }) const serializer2 = jest.fn(customArgsSerializer) @@ -862,7 +874,7 @@ describe('custom serializeQueryArgs per endpoint', () => { const injectedApi = api.injectEndpoints({ endpoints: (build) => ({ injectedQueryWithCustomSerializer: build.query({ - query: () => ({ url: '/success' }), + query: (arg) => `${arg}`, serializeQueryArgs: serializer2, }), }), @@ -875,12 +887,11 @@ describe('custom serializeQueryArgs per endpoint', () => { injectedApi.endpoints.injectedQueryWithCustomSerializer.initiate(5) ) - const firstRef = - injectedApi.endpoints.injectedQueryWithCustomSerializer.select(5)( - storeRef.store.getState() - ) expect(serializer2).toHaveBeenCalled() - - expect(firstRef.data).toEqual({ value: 'success' }) + expect( + storeRef.store.getState().api.queries[ + 'injectedQueryWithCustomSerializer-5' + ] + ).toBeTruthy() }) })