Skip to content

Commit c87a59a

Browse files
authored
Merge pull request #4671 from pierroberto/feat/4644-tags-invalidation-improvements
Feat/4644 tags invalidation improvements
2 parents 4d70f2c + a9def39 commit c87a59a

File tree

7 files changed

+85
-15
lines changed

7 files changed

+85
-15
lines changed

packages/toolkit/src/query/core/buildMiddleware/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function buildMiddleware<
5050

5151
const actions = {
5252
invalidateTags: createAction<
53-
Array<TagTypes | FullTagDescription<TagTypes>>
53+
Array<TagTypes | FullTagDescription<TagTypes> | null | undefined>
5454
>(`${reducerPath}/invalidateTags`),
5555
}
5656

packages/toolkit/src/query/core/buildSelectors.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type {
99
TagTypesFrom,
1010
} from '../endpointDefinitions'
1111
import { expandTagDescription } from '../endpointDefinitions'
12-
import { flatten } from '../utils'
12+
import { flatten, isNotNullish } from '../utils'
1313
import type {
1414
MutationSubState,
1515
QueryCacheKey,
@@ -206,15 +206,15 @@ export function buildSelectors<
206206

207207
function selectInvalidatedBy(
208208
state: RootState,
209-
tags: ReadonlyArray<TagDescription<string>>,
209+
tags: ReadonlyArray<TagDescription<string> | null | undefined>,
210210
): Array<{
211211
endpointName: string
212212
originalArgs: any
213213
queryCacheKey: QueryCacheKey
214214
}> {
215215
const apiState = state[reducerPath]
216216
const toInvalidate = new Set<QueryCacheKey>()
217-
for (const tag of tags.map(expandTagDescription)) {
217+
for (const tag of tags.filter(isNotNullish).map(expandTagDescription)) {
218218
const provided = apiState.provided[tag.type]
219219
if (!provided) {
220220
continue

packages/toolkit/src/query/core/module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ export interface ApiModules<
350350
* ```
351351
*/
352352
invalidateTags: ActionCreatorWithPayload<
353-
Array<TagDescription<TagTypes>>,
353+
Array<TagDescription<TagTypes> | null | undefined>,
354354
string
355355
>
356356

@@ -361,7 +361,7 @@ export interface ApiModules<
361361
*/
362362
selectInvalidatedBy: (
363363
state: RootState<Definitions, string, ReducerPath>,
364-
tags: ReadonlyArray<TagDescription<TagTypes>>,
364+
tags: ReadonlyArray<TagDescription<TagTypes> | null | undefined>,
365365
) => Array<{
366366
endpointName: string
367367
originalArgs: any

packages/toolkit/src/query/endpointDefinitions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
OmitFromUnion,
3030
UnwrapPromise,
3131
} from './tsHelpers'
32+
import { isNotNullish } from './utils'
3233

3334
const resultType = /* @__PURE__ */ Symbol()
3435
const baseQuery = /* @__PURE__ */ Symbol()
@@ -224,7 +225,7 @@ export type GetResultDescriptionFn<
224225
error: ErrorType | undefined,
225226
arg: QueryArg,
226227
meta: MetaType,
227-
) => ReadonlyArray<TagDescription<TagTypes>>
228+
) => ReadonlyArray<TagDescription<TagTypes> | undefined | null>
228229

229230
export type FullTagDescription<TagType> = {
230231
type: TagType
@@ -242,7 +243,7 @@ export type ResultDescription<
242243
ErrorType,
243244
MetaType,
244245
> =
245-
| ReadonlyArray<TagDescription<TagTypes>>
246+
| ReadonlyArray<TagDescription<TagTypes> | undefined | null>
246247
| GetResultDescriptionFn<TagTypes, ResultType, QueryArg, ErrorType, MetaType>
247248

248249
type QueryTypes<
@@ -778,6 +779,7 @@ export function calculateProvidedBy<ResultType, QueryArg, ErrorType, MetaType>(
778779
queryArg,
779780
meta as MetaType,
780781
)
782+
.filter(isNotNullish)
781783
.map(expandTagDescription)
782784
.map(assertTagTypes)
783785
}

packages/toolkit/src/query/tests/buildMiddleware.test.tsx

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createApi } from '@reduxjs/toolkit/query'
2-
import { actionsReducer, setupApiStore } from '../../tests/utils/helpers'
32
import { delay } from 'msw'
3+
import { actionsReducer, setupApiStore } from '../../tests/utils/helpers'
44

55
const baseQuery = (args?: any) => ({ data: args })
66
const api = createApi({
@@ -25,9 +25,15 @@ const api = createApi({
2525
},
2626
providesTags: ['Bread'],
2727
}),
28+
invalidateFruit: build.mutation({
29+
query: (fruit?: 'Banana' | 'Bread' | null) => ({ url: `invalidate/fruit/${fruit || ''}` }),
30+
invalidatesTags(result, error, arg) {
31+
return [arg]
32+
}
33+
})
2834
}),
2935
})
30-
const { getBanana, getBread } = api.endpoints
36+
const { getBanana, getBread, invalidateFruit } = api.endpoints
3137

3238
const storeRef = setupApiStore(api, {
3339
...actionsReducer,
@@ -70,3 +76,61 @@ it('invalidates the specified tags', async () => {
7076
getBread.matchFulfilled,
7177
)
7278
})
79+
80+
it('invalidates tags correctly when null or undefined are provided as tags', async() =>{
81+
await storeRef.store.dispatch(getBanana.initiate(1))
82+
await storeRef.store.dispatch(api.util.invalidateTags([undefined, null, 'Banana']))
83+
84+
// Slight pause to let the middleware run and such
85+
await delay(20)
86+
87+
const apiActions = [
88+
api.internalActions.middlewareRegistered.match,
89+
getBanana.matchPending,
90+
getBanana.matchFulfilled,
91+
api.util.invalidateTags.match,
92+
getBanana.matchPending,
93+
getBanana.matchFulfilled,
94+
]
95+
96+
expect(storeRef.store.getState().actions).toMatchSequence(...apiActions)
97+
})
98+
99+
100+
it.each([
101+
{ tags: [undefined, null, 'Bread'] as Parameters<typeof api.util.invalidateTags>['0'] },
102+
{ tags: [undefined, null], }, { tags: [] }]
103+
)('does not invalidate with tags=$tags if no query matches', async ({ tags }) => {
104+
await storeRef.store.dispatch(getBanana.initiate(1))
105+
await storeRef.store.dispatch(api.util.invalidateTags(tags))
106+
107+
// Slight pause to let the middleware run and such
108+
await delay(20)
109+
110+
const apiActions = [
111+
api.internalActions.middlewareRegistered.match,
112+
getBanana.matchPending,
113+
getBanana.matchFulfilled,
114+
api.util.invalidateTags.match,
115+
]
116+
117+
expect(storeRef.store.getState().actions).toMatchSequence(...apiActions)
118+
})
119+
120+
it.each([{ mutationArg: 'Bread' as "Bread" | null | undefined }, { mutationArg: undefined }, { mutationArg: null }])('does not invalidate queries when a mutation with tags=[$mutationArg] runs and does not match anything', async ({ mutationArg }) => {
121+
await storeRef.store.dispatch(getBanana.initiate(1))
122+
await storeRef.store.dispatch(invalidateFruit.initiate(mutationArg))
123+
124+
// Slight pause to let the middleware run and such
125+
await delay(20)
126+
127+
const apiActions = [
128+
api.internalActions.middlewareRegistered.match,
129+
getBanana.matchPending,
130+
getBanana.matchFulfilled,
131+
invalidateFruit.matchPending,
132+
invalidateFruit.matchFulfilled,
133+
]
134+
135+
expect(storeRef.store.getState().actions).toMatchSequence(...apiActions)
136+
})

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ describe('type tests', () => {
3939

4040
expectTypeOf(api.util.invalidateTags)
4141
.parameter(0)
42-
.toEqualTypeOf<TagDescription<never>[]>()
42+
.toEqualTypeOf<(null | undefined | TagDescription<never>)[]>()
4343
})
4444

4545
describe('endpoint definition typings', () => {

packages/toolkit/src/query/tests/invalidation.test.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ const tagTypes = [
1414
'giraffe',
1515
] as const
1616
type TagTypes = (typeof tagTypes)[number]
17-
type Tags = TagDescription<TagTypes>[]
18-
17+
type ProvidedTags = TagDescription<TagTypes>[]
18+
type InvalidatesTags = (ProvidedTags[number] | null | undefined)[]
1919
/** providesTags, invalidatesTags, shouldInvalidate */
20-
const caseMatrix: [Tags, Tags, boolean][] = [
20+
const caseMatrix: [ProvidedTags, InvalidatesTags, boolean][] = [
2121
// *****************************
2222
// basic invalidation behavior
2323
// *****************************
@@ -39,7 +39,11 @@ const caseMatrix: [Tags, Tags, boolean][] = [
3939
// type + id invalidates type + id
4040
[[{ type: 'apple', id: 1 }], [{ type: 'apple', id: 1 }], true],
4141
[[{ type: 'apple', id: 1 }], [{ type: 'apple', id: 2 }], false],
42-
42+
// null and undefined
43+
[['apple'], [null], false],
44+
[['apple'], [undefined], false],
45+
[['apple'], [null, 'apple'], true],
46+
[['apple'], [undefined, 'apple'], true],
4347
// *****************************
4448
// test multiple values in array
4549
// *****************************

0 commit comments

Comments
 (0)