Master server state management with TanStack Query in Google Antigravity including caching mutations and optimistic updates
# TanStack Query Data Fetching Patterns for Google Antigravity
Server state management differs fundamentally from client state, requiring specialized solutions for caching, synchronization, and background updates. This guide establishes patterns for integrating TanStack Query with Google Antigravity projects, enabling Gemini 3 to generate robust data fetching implementations.
## Query Client Configuration
Set up TanStack Query with optimal defaults:
```typescript
// lib/query-client.ts
import { QueryClient } from "@tanstack/react-query";
export function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
gcTime: 5 * 60 * 1000, // 5 minutes (formerly cacheTime)
retry: (failureCount, error) => {
// Don't retry on 4xx errors
if (error instanceof Error && "status" in error) {
const status = (error as { status: number }).status;
if (status >= 400 && status < 500) return false;
}
return failureCount < 3;
},
refetchOnWindowFocus: process.env.NODE_ENV === "production",
},
mutations: {
retry: 1,
onError: (error) => {
console.error("Mutation error:", error);
},
},
},
});
}
// Singleton for client-side
let browserQueryClient: QueryClient | undefined;
export function getQueryClient() {
if (typeof window === "undefined") {
// Server: always make a new query client
return makeQueryClient();
}
// Browser: reuse existing client
if (!browserQueryClient) {
browserQueryClient = makeQueryClient();
}
return browserQueryClient;
}
```
## Custom Query Hooks
Create type-safe, reusable query hooks:
```typescript
// hooks/queries/useUsers.ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "@/lib/api";
import type { User, CreateUserInput, UpdateUserInput } from "@/types";
export const userKeys = {
all: ["users"] as const,
lists: () => [...userKeys.all, "list"] as const,
list: (filters: Record<string, unknown>) => [...userKeys.lists(), filters] as const,
details: () => [...userKeys.all, "detail"] as const,
detail: (id: string) => [...userKeys.details(), id] as const,
};
export function useUsers(filters?: { role?: string; status?: string }) {
return useQuery({
queryKey: userKeys.list(filters || {}),
queryFn: () => api.users.list(filters),
select: (data) => data.users,
});
}
export function useUser(id: string) {
return useQuery({
queryKey: userKeys.detail(id),
queryFn: () => api.users.get(id),
enabled: !!id,
});
}
export function useCreateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (input: CreateUserInput) => api.users.create(input),
onSuccess: (newUser) => {
// Invalidate list queries
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
// Optionally set the new user in cache
queryClient.setQueryData(userKeys.detail(newUser.id), newUser);
},
});
}
export function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, ...data }: UpdateUserInput & { id: string }) =>
api.users.update(id, data),
onMutate: async ({ id, ...data }) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: userKeys.detail(id) });
// Snapshot previous value
const previousUser = queryClient.getQueryData<User>(userKeys.detail(id));
// Optimistically update
if (previousUser) {
queryClient.setQueryData(userKeys.detail(id), {
...previousUser,
...data,
});
}
return { previousUser };
},
onError: (err, { id }, context) => {
// Rollback on error
if (context?.previousUser) {
queryClient.setQueryData(userKeys.detail(id), context.previousUser);
}
},
onSettled: (data, error, { id }) => {
// Refetch to ensure consistency
queryClient.invalidateQueries({ queryKey: userKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
},
});
}
```
## Infinite Queries
Implement pagination with infinite scroll:
```typescript
// hooks/queries/usePosts.ts
import { useInfiniteQuery } from "@tanstack/react-query";
import { api } from "@/lib/api";
export function useInfinitePosts(categoryId?: string) {
return useInfiniteQuery({
queryKey: ["posts", "infinite", { categoryId }],
queryFn: ({ pageParam }) =>
api.posts.list({ cursor: pageParam, limit: 20, categoryId }),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor,
select: (data) => ({
pages: data.pages,
posts: data.pages.flatMap((page) => page.posts),
}),
});
}
// Component usage
export function PostFeed({ categoryId }: { categoryId?: string }) {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useInfinitePosts(categoryId);
return (
<div>
{data?.posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
{hasNextPage && (
<button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
{isFetchingNextPage ? "Loading..." : "Load More"}
</button>
)}
</div>
);
}
```
## Best Practices
1. **Query key factories**: Use structured key factories for consistency
2. **Optimistic updates**: Provide instant feedback for better UX
3. **Error boundaries**: Handle query errors gracefully
4. **Prefetching**: Prefetch data for anticipated navigation
5. **Selective invalidation**: Invalidate only affected queriesThis TanStack Query prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their tanstack query implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For TanStack Query projects, consider mentioning your framework version, coding style, and any specific libraries you're using.