Summary
RTK Query uses tags to manage cache invalidation automatically.
A tag is a simple string (or an object with type and id) attached to a query result. When a mutation declares that it provides or invalidates specific tags, RTK Query knows which cached queries must be refreshed.
Root Cause
The need for tags stems from the cache coherence problem:
- Queries cache data locally.
- Mutations change data on the server.
- Without a mechanism, the UI would continue to show stale data.
Tags give RTK Query a declarative way to link queries ↔ mutations, solving stale‑cache bugs.
Why This Happens in Real Systems
In production apps:
- Multiple components request the same resource (e.g., a list of posts).
- Separate components trigger updates (create, edit, delete).
- Manually tracking which cached entries to refetch is error‑prone and leads to race conditions or unnecessary network traffic.
Tags let the library automate this relationship, keeping the cache consistent without boiler‑plate.
Real-World Impact
- Performance: Only the queries that truly depend on changed data are refetched, reducing bandwidth.
- Correctness: Guarantees UI reflects the latest server state, preventing stale‑data bugs.
- Developer velocity: Eliminates hand‑written
dispatch(fetchPosts())after every mutation.
Example or Code (if necessary and relevant)
// Define an API slice
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Post'],
endpoints: (builder) => ({
// Query provides the 'Post' tag
getPosts: builder.query({
query: () => 'posts',
providesTags: (result) =>
result
? // each post gets its own tag
[{ type: 'Post', id: 'LIST' }, ...result.map(({ id }) => ({ type: 'Post', id }))]
: [{ type: 'Post', id: 'LIST' }],
}),
// Mutation invalidates the list and specific post
addPost: builder.mutation<Post, Partial>({
query: (body) => ({
url: 'posts',
method: 'POST',
body,
}),
invalidatesTags: [{ type: 'Post', id: 'LIST' }],
}),
updatePost: builder.mutation<Post, Partial>({
query: ({ id, ...patch }) => ({
url: `posts/${id}`,
method: 'PATCH',
body: patch,
}),
invalidatesTags: (result, error, { id }) => [{ type: 'Post', id }],
}),
}),
});
How Senior Engineers Fix It
- Define tag types once in the API slice (
tagTypes: ['Post', 'User']). - Consistently use
providesTagson every read endpoint. - Prefer granular invalidation (
[{ type, id }]) over blanket list invalidation to minimize network calls. - Leverage automatic refetching by setting
refetchOnMountOrArgChangeonly when needed. - Write unit tests that assert cache updates after mutations.
Why Juniors Miss It
- Treat caching as a black box and forget that mutations need to tell the cache what changed.
- Use generic string tags without IDs, causing over‑invalidations or missed updates.
- Skip
providesTagson queries, leaving the cache unaware of what data it holds. - Rely on manual
dispatchcalls after each mutation, leading to duplicated logic and bugs.