selectInvalidatedBy examples
Examples: const entries = api.util.selectInvalidatedBy(state, ['Post']); const entries = api.util.selectInvalidatedBy(state, [{ type: 'Post', id: 1 }]); const entries = api.util.selectInvalidatedBy(state, [{ type: 'Post', id: 1 }, { type: 'Post', id: 4 }]);
invalidateTags signature and parameters
invalidateTags is a Redux action creator with signature: const invalidateTags = (tags: Array<TagTypes | FullTagDescription<TagTypes>>) => ({ type: string, payload: tags }). It accepts an array of tags to be invalidated, where the provided TagType is one of the strings provided to the tagTypes property of the api, e.g. [TagType], [{ type: TagType }], [{ type: TagType, id: number | string }].
invalidateTags behavior
invalidateTags returns an action with the tags as a payload, and the corresponding invalidateTags action type for the api. Dispatching the result of this action creator will invalidate the given tags, causing queries to automatically re-fetch if they are subscribed to cache data that provides the corresponding tags.
selectInvalidatedBy signature and parameters
selectInvalidatedBy is a selector function with signature: function selectInvalidatedBy(state: RootState, tags: ReadonlyArray<TagDescription<string>>): Array<{ endpointName: string, originalArgs: any, queryCacheKey: QueryCacheKey }>. Parameters are: state (the root state), tags (a readonly array of invalidated tags, where the provided TagDescription is one of the strings provided to the tagTypes property of the api, e.g. [TagType], [{ type: TagType }], [{ type: TagType, id: number | string }]).
invalidateTags examples
Examples: dispatch(api.util.invalidateTags(['Post'])); dispatch(api.util.invalidateTags([{ type: 'Post', id: 1 }])); dispatch(api.util.invalidateTags([{ type: 'Post', id: 1 }, { type: 'Post', id: 'LIST' }]));
selectInvalidatedBy return value
selectInvalidatedBy returns an array that contains the endpoint name, the original args, and the queryCacheKey.
invalidateTags function signature
invalidateTags is an ActionCreatorWithPayload that takes an array of TagTypes or FullTagDescription<TagTypes> and returns a string.
RTK Query invalidation strategy
RTK Query uses a declarative invalidation strategy by type and/or type/id, allowing mutations to specify which queries should be invalidated.
RTK Query mutations automatically invalidate queries
RTK Query's centralized API slice definition allows mutations to automatically invalidate and refetch queries on trigger, enabling tightly integrated cache behavior.
invalidateTags query cache invalidation logic
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))
}
}
}
})
This example shows how invalidateTags iterates through invalidated queries and either removes the cached result if no subscriptions exist, or refetches if the query is not uninitialized.
invalidationByTags handler overview
InvalidationByTagsHandler is a middleware handler instantiated during the BuildMiddleware step. It executes in response to matching internal asyncThunk actions. It acts as middleware that processes invalidation sequences.
invalidationByTags matchers for asyncThunk actions
The matchers used for an invalidation sequence are: isThunkActionWithTags matches isFulfilled or isRejectedWithValue on mutationThunk; isQueryEnd matches isFulfilled or isRejected on mutationThunk or queryThunk.
invalidationByTags three core triggers
The handler has three triggers: (1) Mutation trigger when a mutation thunk with tags is fulfilled or rejected with value, (2) Query trigger when a query or mutation thunk is fulfilled or rejected, and (3) Manual invalidation trigger via api.util.invalidateTags. Conditionals 1 and 3 calculate tags from payload or action and endpointDefinition respectively.
invalidationByTags core sequence
The core sequence is: (1) invalidateTags is called with tags from action metadata; queryThunk resolutions always receive an empty set of tags. (2) Calculated tags are added to pending tags list. (3) If invalidationBehavior is 'delayed' and pending thunks/queries are running, the function ends. (4) Pending tags reset to empty list; if no tags remain the function ends. (5) selectInvalidatedBy selects all {endpointName, originalArgs, queryCacheKey} combinations invalidated by specific tags. (6) For each queryCacheKey, either removes the cached query result via removeQueryResult action if no subscription is active, or if query is uninitialized initiates a refetchQuery action. Step 6 executes within context.batch().
invalidationBehavior delayed vs immediate
RTK Query supports invalidationBehavior as 'immediate' or 'delayed', configured on createApi. The new default is 'delayed'. With 'delayed' behavior, any invalidation triggered while a query or mutation is pending batches the invalidation until no query or mutation is running. Set to 'immediate' to revert to RTK 1.9 behavior. The 'delayed' behavior is implemented by a check in invalidateTags that returns early if invalidationBehavior is 'delayed' and hasPendingRequests is true.
invalidationByTags handler code example
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,
)
}
}
This example shows the main handler logic that detects which trigger occurred and calls invalidateTags with appropriate tags.
invalidateTags delayed invalidation implementation
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
}
This example shows the beginning of the invalidateTags function that collects new tags into pending invalidations and returns early if delayed invalidation behavior is enabled and requests are pending.
invalidationSlice removeQueryResult handling
When querySlice.actions.removeQueryResult is dispatched, the invalidationSlice deletes the relevant queryCacheKey entry from the list of subscription ids.
invalidationSlice queryThunk fulfilled/rejected handling
When queryThunk.fulfilled or queryThunk.rejected is handled in the invalidationSlice, it gets the list of tags from the action and endpoint definition, gets the queryCacheKey, and calls the updateProvidedBy action.
invalidationByTags matches queryThunk outcomes
The invalidationByTags middleware matches against all rejected and fulfilled cases for queryThunk.
Tag invalidation and refetching behavior
When a mutation invalidates tags, if cache data is being invalidated, it will either refetch the providing query (if components are still using that data) or remove the data from the cache. This enables designing an API such that firing a specific mutation will cause query endpoints to consider their cached data invalid and re-fetch the data if there is an active subscription.
Specific tag invalidation matching rules
A specific tag (e.g. [{type: 'Post', id: 1}]) will invalidate only provided tags with both the matching type and matching id. It will not cause a general tag to be invalidated directly. For example, if [{type: 'Post', id: 1}] is invalidated, it invalidates [{type: 'Post', id: 1}] and [{type: 'Post', id: 1}, {type: 'User'}], but not ['Post'], [{type: 'Post'}], or [{type: 'Post', id: 2}].
LIST tag pattern for selective invalidation
The 'LIST' id is an arbitrary string used as a label for data provided by a bulk query, allowing mutations to invalidate list queries separately from individual item queries. This enables using an id like 'LIST' alongside entity IDs for individual items. The 'LIST' is an arbitrary choice; you could use 'ALL' or '*' instead. The important thing is ensuring the id does not collide with ids returned by query results.
Using abstract tag IDs for granular invalidation
The id property of a tag is not limited to database IDs alone. It is simply a way to label a subset of a particular collection of data for a particular tag type. Common patterns include: using 'LIST' for bulk queries and entity IDs for individual items, using multiple abstract IDs like 'SVELTE_POSTS' and 'REACT_POSTS' for additional control, or adding another tagType instead if the concept of abstract ids seems strange.
Providing error tags for failed queries
Tags can be provided not only for successful query results but also for failed queries. When a query fails, you can provide specific tags (such as 'UNAUTHORIZED' or 'UNKNOWN_ERROR') that indicate the type of failure. A separate mutation can then invalidate these error tags to trigger a re-attempt of the previously failed endpoints if a component is still subscribed.
Cache tag matching matrix for provided vs invalidated tags
General tag A ['Post'] / [{type: 'Post'}] invalidates: ['Post'], [{type: 'Post'}], [{type: 'Post', id: 1}], [{type: 'Post', id: 'LIST'}]. General tag B ['User'] / [{type: 'User'}] invalidates: ['User'], [{type: 'User'}], [{type: 'User', id: 1}], [{type: 'User', id: 'LIST'}]. Specific tag [{type: 'Post', id: 1}] invalidates: only [{type: 'Post', id: 1}]. Specific tag [{type: 'Post', id: 'LIST'}] invalidates: only [{type: 'Post', id: 'LIST'}]. Specific tags do not invalidate general tags of the same type.
Example: invalidating tags on mutation
addPost: build.mutation<Post, Omit<Post, 'id'>>({
query: (body) => ({
url: 'post',
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
}),
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
invalidatesTags: ['Post'],
})
Example: LIST tag pattern for selective invalidation
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: (result) =>
result
? [
...result.map(({ id }) => ({ type: 'Posts' as const, id })),
{ type: 'Posts', id: 'LIST' },
]
: [{ type: 'Posts', id: 'LIST' }],
}),
addPost: build.mutation<Post, Partial<Post>>({
query(body) {
return {
url: `post`,
method: 'POST',
body,
}
},
invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
}),
getPost: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
})
Example: providing error tags for failed queries
postById: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: (result, error, id) =>
result
? [{ type: 'Post', id }]
: error?.status === 401
? ['UNAUTHORIZED']
: ['UNKNOWN_ERROR'],
}),
login: build.mutation<LoginResponse, void>({
query: () => '/login',
invalidatesTags: (result) => (result ? ['UNAUTHORIZED'] : []),
}),
refetchErroredQueries: build.mutation<null, void>({
queryFn: () => ({ data: null }),
invalidatesTags: ['UNKNOWN_ERROR'],
})
Example: helper function for list tag pattern
function providesList<R extends { id: string | number }[], T extends string>(
resultsWithIds: R | undefined,
tagType: T,
) {
return resultsWithIds
? [
{ type: tagType, id: 'LIST' },
...resultsWithIds.map(({ id }) => ({ type: tagType, id })),
]
: [{ type: tagType, id: 'LIST' }]
}
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => `posts`,
providesTags: (result) => providesList(result, 'Post'),
}),
getUsers: build.query({
query: () => `users`,
providesTags: (result) => providesList(result, 'User'),
}),
}),
})
Invalidation scenario: when a mutation fires with provided tags
When a query endpoint stores received data in cache and registers provided tags, and then a mutation fires and invalidates those tags, the following happens: (1) RTK Query registers that the tag is now invalidated and removes the previously provided tags from the cache. (2) If the query endpoint has provided tags which are now invalidated and a component is still subscribed to that data, the query is automatically fired off again, fetching new data and registering new tags for the updated cached data.
Example: invalidating specific tag IDs
editPost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
query: (body) => ({
url: `post/${body.id}`,
method: 'POST',
body,
}),
invalidatesTags: (result, error, arg) => [{ type: 'Post', id: arg.id }],
})
Pitfall: partial list pagination with tag invalidation
When using tag invalidation with paginated queries, a pitfall is that a paginated query may only provide tags for entity IDs that fall on the currently shown page. If an entity is deleted from an earlier page, the paginated query will not be providing a tag for that specific ID, so it will not be invalidated to trigger re-fetching. This can result in incorrect item counts or page totals.
Using PARTIAL-LIST tag for paginated query invalidation
To overcome the partial list pagination pitfall, use a strategy where the paginated query provides a tag with type 'Posts' and id 'PARTIAL-LIST' in addition to tags for individual items. Any mutation that should affect the paginated data (like delete) should invalidate both the specific item tag and the 'PARTIAL-LIST' tag. This ensures the paginated query re-fetches even if the deleted item is not currently shown on the page.
Paginated query providesTags implementation with PARTIAL-LIST
The providesTags for a paginated query should map over the results and provide a tag for each item's id, plus a { type: 'Posts', id: 'PARTIAL-LIST' } tag. If there is no result, provide only the PARTIAL-LIST tag. Example: providesTags: (result, error, page) => result ? [...result.data.map(({ id }) => ({ type: 'Posts', id })), { type: 'Posts', id: 'PARTIAL-LIST' }] : [{ type: 'Posts', id: 'PARTIAL-LIST' }]
Mutation invalidatesTags for paginated queries
When a mutation (like deletePost) should affect paginated query results, invalidate both the specific item tag and the PARTIAL-LIST tag. Example: invalidatesTags: (result, error, id) => [{ type: 'Posts', id }, { type: 'Posts', id: 'PARTIAL-LIST' }]