From 3018d96c634cfc3150544ec203086d2234a85ee1 Mon Sep 17 00:00:00 2001 From: Pier Roberto Lucisano Date: Sat, 19 Oct 2024 11:35:09 +0200 Subject: [PATCH 1/5] feat: support nullish tags in invalidateTags and selectInvalidatedBy --- packages/toolkit/src/query/core/buildSelectors.ts | 6 +++--- .../toolkit/src/query/tests/invalidation.test.tsx | 12 ++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/toolkit/src/query/core/buildSelectors.ts b/packages/toolkit/src/query/core/buildSelectors.ts index 83fa9c0103..ee9416ca43 100644 --- a/packages/toolkit/src/query/core/buildSelectors.ts +++ b/packages/toolkit/src/query/core/buildSelectors.ts @@ -9,7 +9,7 @@ import type { TagTypesFrom, } from '../endpointDefinitions' import { expandTagDescription } from '../endpointDefinitions' -import { flatten } from '../utils' +import { flatten, isNotNullish } from '../utils' import type { MutationSubState, QueryCacheKey, @@ -205,7 +205,7 @@ export function buildSelectors< function selectInvalidatedBy( state: RootState, - tags: ReadonlyArray>, + tags: ReadonlyArray | null | undefined>, ): Array<{ endpointName: string originalArgs: any @@ -213,7 +213,7 @@ export function buildSelectors< }> { const apiState = state[reducerPath] const toInvalidate = new Set() - for (const tag of tags.map(expandTagDescription)) { + for (const tag of tags.filter(isNotNullish).map(expandTagDescription)) { const provided = apiState.provided[tag.type] if (!provided) { continue diff --git a/packages/toolkit/src/query/tests/invalidation.test.tsx b/packages/toolkit/src/query/tests/invalidation.test.tsx index 09b71cdee2..03a7638663 100644 --- a/packages/toolkit/src/query/tests/invalidation.test.tsx +++ b/packages/toolkit/src/query/tests/invalidation.test.tsx @@ -14,10 +14,10 @@ const tagTypes = [ 'giraffe', ] as const type TagTypes = (typeof tagTypes)[number] -type Tags = TagDescription[] - +type ProvidedTags = TagDescription[] +type InvalidatesTags = (ProvidedTags[number] | null | undefined)[] /** providesTags, invalidatesTags, shouldInvalidate */ -const caseMatrix: [Tags, Tags, boolean][] = [ +const caseMatrix: [ProvidedTags, InvalidatesTags, boolean][] = [ // ***************************** // basic invalidation behavior // ***************************** @@ -39,7 +39,11 @@ const caseMatrix: [Tags, Tags, boolean][] = [ // type + id invalidates type + id [[{ type: 'apple', id: 1 }], [{ type: 'apple', id: 1 }], true], [[{ type: 'apple', id: 1 }], [{ type: 'apple', id: 2 }], false], - + // null and undefined + [['apple'], [null], false], + [['apple'], [undefined], false], + [['apple'], [null, 'apple'], true], + [['apple'], [undefined, 'apple'], true], // ***************************** // test multiple values in array // ***************************** From 89f96f16eb92cc0f5328f6538451266fffdf8547 Mon Sep 17 00:00:00 2001 From: Pier Roberto Lucisano Date: Sat, 19 Oct 2024 11:38:47 +0200 Subject: [PATCH 2/5] feat: update TS types to support invalidateTags and selectInvalidatedBy changes --- packages/toolkit/src/query/core/buildMiddleware/index.ts | 2 +- packages/toolkit/src/query/core/module.ts | 4 ++-- packages/toolkit/src/query/endpointDefinitions.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/toolkit/src/query/core/buildMiddleware/index.ts b/packages/toolkit/src/query/core/buildMiddleware/index.ts index 17092d82fd..38020bb720 100644 --- a/packages/toolkit/src/query/core/buildMiddleware/index.ts +++ b/packages/toolkit/src/query/core/buildMiddleware/index.ts @@ -48,7 +48,7 @@ export function buildMiddleware< const actions = { invalidateTags: createAction< - Array> + Array | null | undefined> >(`${reducerPath}/invalidateTags`), } diff --git a/packages/toolkit/src/query/core/module.ts b/packages/toolkit/src/query/core/module.ts index 074aa08eb8..6bb43bc411 100644 --- a/packages/toolkit/src/query/core/module.ts +++ b/packages/toolkit/src/query/core/module.ts @@ -350,7 +350,7 @@ export interface ApiModules< * ``` */ invalidateTags: ActionCreatorWithPayload< - Array>, + Array | null | undefined>, string > @@ -361,7 +361,7 @@ export interface ApiModules< */ selectInvalidatedBy: ( state: RootState, - tags: ReadonlyArray>, + tags: ReadonlyArray | null | undefined>, ) => Array<{ endpointName: string originalArgs: any diff --git a/packages/toolkit/src/query/endpointDefinitions.ts b/packages/toolkit/src/query/endpointDefinitions.ts index 38e5343475..fb2a93a430 100644 --- a/packages/toolkit/src/query/endpointDefinitions.ts +++ b/packages/toolkit/src/query/endpointDefinitions.ts @@ -242,7 +242,7 @@ export type ResultDescription< ErrorType, MetaType, > = - | ReadonlyArray> + | ReadonlyArray | undefined | null> | GetResultDescriptionFn type QueryTypes< From eeaa20ca472f0e99ce4f5c8ab248d7e1c8a5b030 Mon Sep 17 00:00:00 2001 From: Pier Roberto Lucisano Date: Sat, 19 Oct 2024 15:49:02 +0200 Subject: [PATCH 3/5] Fix type test --- packages/toolkit/src/query/tests/createApi.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolkit/src/query/tests/createApi.test-d.ts b/packages/toolkit/src/query/tests/createApi.test-d.ts index b33ced1f6e..2b1dc64d9e 100644 --- a/packages/toolkit/src/query/tests/createApi.test-d.ts +++ b/packages/toolkit/src/query/tests/createApi.test-d.ts @@ -39,7 +39,7 @@ describe('type tests', () => { expectTypeOf(api.util.invalidateTags) .parameter(0) - .toEqualTypeOf[]>() + .toEqualTypeOf<(null | undefined | TagDescription)[]>() }) describe('endpoint definition typings', () => { From 459e4ec5bf21edfdd4de5fc38de5a72218bdd83d Mon Sep 17 00:00:00 2001 From: Pier Roberto Lucisano Date: Sat, 19 Oct 2024 16:27:40 +0200 Subject: [PATCH 4/5] Add invalidateTags tests --- .../src/query/tests/buildMiddleware.test.tsx | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/toolkit/src/query/tests/buildMiddleware.test.tsx b/packages/toolkit/src/query/tests/buildMiddleware.test.tsx index 0e52cb0cd1..53e19c9be8 100644 --- a/packages/toolkit/src/query/tests/buildMiddleware.test.tsx +++ b/packages/toolkit/src/query/tests/buildMiddleware.test.tsx @@ -1,6 +1,6 @@ import { createApi } from '@reduxjs/toolkit/query' -import { actionsReducer, setupApiStore } from '../../tests/utils/helpers' import { delay } from 'msw' +import { actionsReducer, setupApiStore } from '../../tests/utils/helpers' const baseQuery = (args?: any) => ({ data: args }) const api = createApi({ @@ -70,3 +70,41 @@ it('invalidates the specified tags', async () => { getBread.matchFulfilled, ) }) + +it('invalidates tags correctly when null or undefined are provided as tags', async() =>{ + await storeRef.store.dispatch(getBanana.initiate(1)) + await storeRef.store.dispatch(api.util.invalidateTags([undefined, null, 'Banana'])) + + // Slight pause to let the middleware run and such + await delay(20) + + const apiActions = [ + api.internalActions.middlewareRegistered.match, + getBanana.matchPending, + getBanana.matchFulfilled, + api.util.invalidateTags.match, + getBanana.matchPending, + getBanana.matchFulfilled, + ] + + expect(storeRef.store.getState().actions).toMatchSequence(...apiActions) +}) + + +it.each([{ tags: [undefined, null, 'Bread'] as Parameters['0'] }, { tags: [undefined, null], }, { tags: [] }])('does not invalidate with tags=$tags if no query matches', async ({ tags }) => { + await storeRef.store.dispatch(getBanana.initiate(1)) + await storeRef.store.dispatch(api.util.invalidateTags(tags)) + + // Slight pause to let the middleware run and such + await delay(20) + + const apiActions = [ + api.internalActions.middlewareRegistered.match, + getBanana.matchPending, + getBanana.matchFulfilled, + api.util.invalidateTags.match, + ] + + expect(storeRef.store.getState().actions).toMatchSequence(...apiActions) +}) + \ No newline at end of file From a9def3987111bcb16e23910e173639f46c1cda41 Mon Sep 17 00:00:00 2001 From: Pier Roberto Lucisano Date: Sat, 19 Oct 2024 17:22:40 +0200 Subject: [PATCH 5/5] Support null or undefined when invalidateTags is a function --- .../toolkit/src/query/endpointDefinitions.ts | 4 ++- .../src/query/tests/buildMiddleware.test.tsx | 32 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/toolkit/src/query/endpointDefinitions.ts b/packages/toolkit/src/query/endpointDefinitions.ts index fb2a93a430..8f7955468c 100644 --- a/packages/toolkit/src/query/endpointDefinitions.ts +++ b/packages/toolkit/src/query/endpointDefinitions.ts @@ -29,6 +29,7 @@ import type { OmitFromUnion, UnwrapPromise, } from './tsHelpers' +import { isNotNullish } from './utils' const resultType = /* @__PURE__ */ Symbol() const baseQuery = /* @__PURE__ */ Symbol() @@ -224,7 +225,7 @@ export type GetResultDescriptionFn< error: ErrorType | undefined, arg: QueryArg, meta: MetaType, -) => ReadonlyArray> +) => ReadonlyArray | undefined | null> export type FullTagDescription = { type: TagType @@ -778,6 +779,7 @@ export function calculateProvidedBy( queryArg, meta as MetaType, ) + .filter(isNotNullish) .map(expandTagDescription) .map(assertTagTypes) } diff --git a/packages/toolkit/src/query/tests/buildMiddleware.test.tsx b/packages/toolkit/src/query/tests/buildMiddleware.test.tsx index 53e19c9be8..0a03396302 100644 --- a/packages/toolkit/src/query/tests/buildMiddleware.test.tsx +++ b/packages/toolkit/src/query/tests/buildMiddleware.test.tsx @@ -25,9 +25,15 @@ const api = createApi({ }, providesTags: ['Bread'], }), + invalidateFruit: build.mutation({ + query: (fruit?: 'Banana' | 'Bread' | null) => ({ url: `invalidate/fruit/${fruit || ''}` }), + invalidatesTags(result, error, arg) { + return [arg] + } + }) }), }) -const { getBanana, getBread } = api.endpoints +const { getBanana, getBread, invalidateFruit } = api.endpoints const storeRef = setupApiStore(api, { ...actionsReducer, @@ -91,7 +97,10 @@ it('invalidates tags correctly when null or undefined are provided as tags', asy }) -it.each([{ tags: [undefined, null, 'Bread'] as Parameters['0'] }, { tags: [undefined, null], }, { tags: [] }])('does not invalidate with tags=$tags if no query matches', async ({ tags }) => { +it.each([ + { tags: [undefined, null, 'Bread'] as Parameters['0'] }, + { tags: [undefined, null], }, { tags: [] }] +)('does not invalidate with tags=$tags if no query matches', async ({ tags }) => { await storeRef.store.dispatch(getBanana.initiate(1)) await storeRef.store.dispatch(api.util.invalidateTags(tags)) @@ -107,4 +116,21 @@ it.each([{ tags: [undefined, null, 'Bread'] as Parameters { + await storeRef.store.dispatch(getBanana.initiate(1)) + await storeRef.store.dispatch(invalidateFruit.initiate(mutationArg)) + + // Slight pause to let the middleware run and such + await delay(20) + + const apiActions = [ + api.internalActions.middlewareRegistered.match, + getBanana.matchPending, + getBanana.matchFulfilled, + invalidateFruit.matchPending, + invalidateFruit.matchFulfilled, + ] + + expect(storeRef.store.getState().actions).toMatchSequence(...apiActions) +}) \ No newline at end of file