TanStack Query & Server State Management
TanStack Query (React Query) is a dedicated server-state management library providing declarative data fetching, automatic caching, deduplication, background revalidation (SWR), and garbage collection.
Why it matters in interviews
Visual & Interactive Explanation
Component Mounts
useQuery(['user', id])
Cache Check
Return cached data immediately if present
Is Data Stale?
If now - updatedAt > staleTime
Background Fetch
Execute queryFn without blocking UI
Structural Sharing
Diff new payload against cache
Selective Render
Update cache & trigger re-render only if mutated
Code Examples & Implementation
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
// Standard query key factory pattern
export const todoKeys = {
all: ["todos"] as const,
lists: () => [...todoKeys.all, "list"] as const,
list: (filters: string) => [...todoKeys.lists(), { filters }] as const,
details: () => [...todoKeys.all, "detail"] as const,
detail: (id: string) => [...todoKeys.details(), id] as const,
};
export function useTodoList(filter: string) {
return useQuery({
queryKey: todoKeys.list(filter),
queryFn: () => fetch(`/api/todos?filter=${filter}`).then((r) => r.json()),
staleTime: 1000 * 60 * 2, // 2 minutes fresh
gcTime: 1000 * 60 * 10, // 10 minutes retention
});
}
export function useAddTodo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (newTodo: { title: string }) =>
fetch("/api/todos", { method: "POST", body: JSON.stringify(newTodo) }),
onSuccess: () => {
// Invalidate all todo lists without invalidating unrelated keys
queryClient.invalidateQueries({ queryKey: todoKeys.lists() });
},
});
}export function useToggleTodo() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, completed }: { id: string; completed: boolean }) =>
fetch(`/api/todos/${id}`, {
method: "PATCH",
body: JSON.stringify({ completed }),
}),
onMutate: async ({ id, completed }) => {
// Cancel outgoing refetches to avoid overwriting optimistic update
await queryClient.cancelQueries({ queryKey: ["todos"] });
// Snapshot the previous value
const previousTodos = queryClient.getQueryData<Todo[]>(["todos"]);
// Optimistically update the cache
queryClient.setQueryData<Todo[]>(["todos"], (old) =>
old?.map((t) => (t.id === id ? { ...t, completed } : t))
);
// Return context with snapshot
return { previousTodos };
},
onError: (_err, _variables, context) => {
// Rollback to snapshot on network failure
if (context?.previousTodos) {
queryClient.setQueryData(["todos"], context.previousTodos);
}
},
onSettled: () => {
// Always re-sync with server truth
queryClient.invalidateQueries({ queryKey: ["todos"] });
},
});
}Interview-Ready Answers
TanStack Query manages asynchronous server state through query caching, automatic deduplication, background refetching (SWR), and granular cache invalidation via query keys.
Follow-up Questions & Deep Dives
🎯 Code Output — What Does This Print?
// Component A and Component B mount simultaneously on screen:
function ComponentA() {
const { data } = useQuery({
queryKey: ["user", "123"],
queryFn: () => fetchUser("123"), // takes 200ms
});
return <div>{data?.name}</div>;
}
function ComponentB() {
const { data } = useQuery({
queryKey: ["user", "123"],
queryFn: () => fetchUser("123"),
});
return <div>{data?.role}</div>;
}
// How many actual network calls are dispatched?// Query has staleTime = 10_000 (10s) and gcTime = 300_000 (5m)
// Component unmounts at t = 5s.
// User navigates back to the page at t = 8s.
// Does useQuery trigger a network fetch?Common Mistakes & Anti-Patterns
- Leaving staleTime at 0 (default), resulting in refetches on every single component mount and window focus
- Putting non-serializable objects or functions inside queryKey arrays
- Using useState + useEffect for data fetching instead of a dedicated cache layer
- Forgetting to return the snapshot context from onMutate during optimistic updates
Real-World Architectural Scenario
In an enterprise order management portal, when users switch between browser tabs, the screen flashes loading spinners on 15 different widgets. How do you fix this?
Rate Your Readiness
Rate how comfortably you can explain this in an interview to update your global readiness gauge: