invalidationByTags
Overview
InvalidationByTagsHandler
is a handler instantiated during the (BuildMiddleware) step of the build. The handler acts as a (Middleware) and executes each step in response to matching of internal asyncThunk actions.
The matchers used for a "invalidation sequence" are these two cases:
const isThunkActionWithTags = isAnyOf(
isFulfilled(mutationThunk),
isRejectedWithValue(mutationThunk),
)
const isQueryEnd = isAnyOf(
isFulfilled(mutationThunk, queryThunk),
isRejected(mutationThunk, queryThunk),
)
Triggers
The handler has 3 core conditionals that trigger a sequence:
Conditional 1 AND 3 are identical in process except the tags are calculated from the payload rather than from the action and endpointDefinition
- Mutation trigger
- Query trigger
- Manual invalidation via
api.util.invalidateTags
trigger
const handler: ApiMiddlewareInternalHandler = (action, mwApi) => {
if (isThunkActionWithTags(action)) {
invalidateTags(
calculateProvidedByThunk(
action,
'invalidatesTags',
endpointDefinitions,
assertTagType,
),
mwApi,
)
} else if (isQueryEnd(action)) {
invalidateTags([], mwApi)
} else if (api.util.invalidateTags.match(action)) {
invalidateTags(
calculateProvidedBy(
action.payload,
undefined,
undefined,
undefined,
undefined,
assertTagType,
),
mwApi,
)
}
}
Core Sequence
invalidateTags()
initiates:- invalidateTags function is called with a list of tags generated from the action metadata
- in the case of a [queryThunk] resolution an empty set of tags is always provided
- The tags calculated are added to the list of pending tags to invalidate (see delayed)
- (optional: 'Delayed') the invalidateTags function is ended if the
apiSlice.invalidationBehavior
is set to "delayed" and there are any pending thunks/queries running in thatapiSlice
- Pending tags are reset to an empty list, if there are no tags the function ends here
- Selects all
{ endpointName, originalArgs, queryCacheKey }
combinations that would be invalidated by a specific set of tags. - Iterates through queryCacheKeys selected and performs one of two actions if the query exists*
- removes cached query result - via the
removeQueryResult
action - if no subscription is active - if the query is "uninitialized" it initiates a
refetchQuery
action
- removes cached query result - via the
const toInvalidate = api.util.selectInvalidatedBy(rootState, tags)
context.batch(() => {
const valuesArray = Array.from(toInvalidate.values())
for (const { queryCacheKey } of valuesArray) {
const querySubState = state.queries[queryCacheKey]
const subscriptionSubState =
internalState.currentSubscriptions[queryCacheKey] ?? {}
if (querySubState) {
if (countObjectKeys(subscriptionSubState) === 0) {
mwApi.dispatch(
removeQueryResult({
queryCacheKey,
}),
)
} else if (querySubState.status !== 'uninitialized' /* uninitialized */) {
mwApi.dispatch(refetchQuery(querySubState, queryCacheKey))
}
}
}
})
Step 6 is performed within a context.batch()
call.
Delayed
RTKQ now has internal logic to delay tag invalidation briefly, to allow multiple invalidations to get handled together. This is controlled by a new invalidationBehavior: 'immediate' | 'delayed'
flag on createApi
. The new default behavior is 'delayed'
. Set it to 'immediate'
to revert to the behavior in RTK 1.9.
The 'delayed'
behavior enables a check inside invalidationByTags
that will cause any invalidation that is triggered while a query/mutation is still pending to batch the invalidation until no query/mutation is running.
function invalidateTags(
newTags: readonly FullTagDescription<string>[],
mwApi: SubMiddlewareApi,
) {
const rootState = mwApi.getState()
const state = rootState[reducerPath]
pendingTagInvalidations.push(...newTags)
if (
state.config.invalidationBehavior === 'delayed' &&
hasPendingRequests(state)
) {
return
}