RTK Query
RTK Query is the data-fetching and caching layer built into Redux Toolkit. It’s the Redux ecosystem’s answer to TanStack Query: define your API once, get auto-generated hooks with caching, dedup, invalidation, polling, and optimistic updates — no hand-written thunks, loading flags, or reducers for server data.
TL;DR
createApi defines endpoints (query for reads, mutation for writes). It generates hooks (useGetPostsQuery, useAddPostMutation) and a reducer/middleware you add to the store. The cache is keyed by endpoint + serialized args; tags drive invalidation (a mutation invalidates tags that queries provide, triggering refetch). Pick RTK Query when you’re already on Redux; pick TanStack Query if you’re not.
Interview Q&A
Q: What does a basic API slice look like?
A:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
tagTypes: ["Post"],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => "posts",
providesTags: ["Post"], // this data is tagged "Post"
}),
addPost: build.mutation<Post, Partial<Post>>({
query: (body) => ({ url: "posts", method: "POST", body }),
invalidatesTags: ["Post"], // invalidate → getPosts refetches
}),
}),
});
export const { useGetPostsQuery, useAddPostMutation } = api;
Add api.reducer under api.reducerPath and api.middleware in configureStore, and you’re done.
Q: How do the generated hooks behave?
A:
const { data, isLoading, isFetching, error, refetch } = useGetPostsQuery();
const [addPost, { isLoading: adding }] = useAddPostMutation();
- Mount → fetch (or serve cache). Two components calling
useGetPostsQuery()share one request (dedup). isLoading= first load;isFetching= any in-flight fetch (including background refetch).- Data is cached by
(endpoint, args). When the last subscriber unmounts, it’s kept forkeepUnusedDataFor(default 60s) then garbage-collected.
Q: How does tag-based invalidation work?
A: Queries declare what they provide, mutations declare what they invalidate. RTK Query refetches any active query whose provided tags were invalidated. For per-item granularity, tag by id:
getPosts: build.query({
query: () => "posts",
providesTags: (result) =>
result ? [...result.map((p) => ({ type: "Post" as const, id: p.id })), { type: "Post", id: "LIST" }]
: [{ type: "Post", id: "LIST" }],
}),
updatePost: build.mutation({
query: (p) => ({ url: `posts/${p.id}`, method: "PUT", body: p }),
invalidatesTags: (r, e, p) => [{ type: "Post", id: p.id }], // refetch just that post
}),
This is the mental model to nail in interviews: providesTags + invalidatesTags = declarative cache invalidation, the RTK analog of TanStack Query’s queryClient.invalidateQueries.
Q: How do you do polling and optimistic updates?
A: Polling is a hook option:
useGetPostsQuery(undefined, { pollingInterval: 5000 });
Optimistic update via onQueryStarted — patch the cache immediately, undo on failure:
updatePost: build.mutation({
query: (p) => ({ url: `posts/${p.id}`, method: "PUT", body: p }),
async onQueryStarted(p, { dispatch, queryFulfilled }) {
const patch = dispatch(api.util.updateQueryData("getPosts", undefined,
(draft) => { const x = draft.find((d) => d.id === p.id); if (x) Object.assign(x, p); }));
try { await queryFulfilled; } catch { patch.undo(); } // rollback on error
},
}),
updateQueryData uses Immer so you “mutate” the draft; patch.undo() reverts. Same pattern, library-specific API — contrast TanStack’s onMutate/onError in ../../11_apis_data_fetching/03_optimistic_updates.md.
Q: RTK Query vs TanStack Query — how do you choose?
A:
| RTK Query | TanStack Query | |
|---|---|---|
| Ecosystem | built into Redux Toolkit | standalone, framework-agnostic |
| Cache location | the Redux store (one devtools) | its own cache |
| API style | declarative endpoints + tags | imperative queryKey + queryFn |
| Best when | already using Redux for client state | no other reason to run Redux |
Both solve server state well. The deciding factor is usually “are we already on Redux?” Don’t add Redux just to get RTK Query — TanStack Query needs no store.
Q: When do you still need regular Redux slices alongside RTK Query?
A: For genuine client state (UI, drafts, cross-cutting flags). RTK Query handles server data; a normal createSlice handles the local stuff. They coexist in the same store.
Gotchas / edge cases
- Forgetting to add the middleware — caching, invalidation, and polling silently don’t work without
api.middlewareinconfigureStore. - Over-broad tags — invalidating
"Post"refetches every post list/detail; tag by id for surgical invalidation. isLoadingvsisFetching— show skeletons onisLoading, a subtle spinner on backgroundisFetching; conflating them flickers the UI.keepUnusedDataFortoo low evicts cache aggressively (refetch on every revisit); too high serves stale data — tune per endpoint.fetchBaseQueryis minimal — for auth refresh/retry, wrap it with a custombaseQuery(baseQueryWithReauth).- It’s still server state — don’t copy RTK Query results into a slice; subscribe to the query.
What a senior is expected to say
- “RTK Query is Redux’s built-in server-state cache:
createApiendpoints generate hooks; the cache is keyed by endpoint+args; tags drive invalidation.” - “providesTags/invalidatesTags is declarative invalidation — tag by id for granularity, or you refetch everything.”
- “Optimistic updates use
onQueryStarted+updateQueryDatawithpatch.undo()on failure.” - “I pick RTK Query when already on Redux, TanStack Query otherwise — I wouldn’t add Redux just for it.”
Cross-references
- Why server data doesn’t belong in hand-written reducers: ../server_vs_client_state.md
- TanStack Query equivalent: ../../11_apis_data_fetching/02_tanstack_query.md
- Optimistic update patterns compared: ../../11_apis_data_fetching/03_optimistic_updates.md
- Redux Toolkit basics (slices, store): redux-toolkit.md
Further reading
- RTK Query overview: https://redux-toolkit.js.org/rtk-query/overview
- RTK Query — Automated re-fetching (tags): https://redux-toolkit.js.org/rtk-query/usage/automated-refetching
- RTK Query — Optimistic updates: https://redux-toolkit.js.org/rtk-query/usage/manual-cache-updates#optimistic-updates