# TanStack Query Patterns
Complete examples for queries, mutations, and infinite queries.
## Query Basics
```typescript
import { useQuery } from "@tanstack/react-query";
import { getUserById } from "~/lib/server/functions/users";
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useQuery({
queryKey: ["user", userId], // Array key for cache
queryFn: () => getUserById(userId),
staleTime: 60000, // Grey Haven default: 1 minute
// Data is "fresh" for 60 seconds, no refetch during this time
});
if (isLoading) return ;
if (error) return ;
if (!user) return ;
return
{user.name}
;
}
```
## Query Key Patterns
Use consistent query key structure:
```typescript
// ✅ Good query keys (specific to general)
queryKey: ["user", userId] // Single user
queryKey: ["users", { tenantId, page: 1 }] // List with filters
queryKey: ["organizations", orgId, "teams"] // Nested resource
// ❌ Bad query keys (inconsistent, not cacheable)
queryKey: [userId] // Missing resource type
queryKey: ["getUser", userId] // Don't include function name
queryKey: [{ id: userId, type: "user" }] // Object first is confusing
```
## Mutations with Optimistic Updates
```typescript
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { updateUser } from "~/lib/server/functions/users";
function EditUserForm({ user }: { user: User }) {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data: UserUpdate) => updateUser(user.id, data),
// Optimistic update (immediate UI feedback)
onMutate: async (newData) => {
// Cancel ongoing queries
await queryClient.cancelQueries({ queryKey: ["user", user.id] });
// Snapshot previous value
const previousUser = queryClient.getQueryData(["user", user.id]);
// Optimistically update cache
queryClient.setQueryData(["user", user.id], (old: User) => ({
...old,
...newData,
}));
return { previousUser };
},
// On error, rollback
onError: (err, newData, context) => {
queryClient.setQueryData(["user", user.id], context.previousUser);
},
// Always refetch after mutation
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["user", user.id] });
},
});
const handleSubmit = (data: UserUpdate) => {
mutation.mutate(data);
};
return (
);
}
```
## Infinite Queries (Pagination)
```typescript
import { useInfiniteQuery } from "@tanstack/react-query";
import { listUsers } from "~/lib/server/functions/users";
function UsersList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ["users"],
queryFn: ({ pageParam = 0 }) => listUsers({
limit: 50,
offset: pageParam,
}),
getNextPageParam: (lastPage, allPages) => {
// Return next offset or undefined if no more pages
return lastPage.length === 50 ? allPages.length * 50 : undefined;
},
initialPageParam: 0,
staleTime: 60000,
});
return (
{data?.pages.map((page, i) => (
{page.map((user) => (
))}
))}
);
}
```
## Prefetching (Performance Optimization)
```typescript
import { useQueryClient } from "@tanstack/react-query";
import { getUserById } from "~/lib/server/functions/users";
function UsersList({ users }: { users: User[] }) {
const queryClient = useQueryClient();
// Prefetch user details on hover
const handleMouseEnter = (userId: string) => {
queryClient.prefetchQuery({
queryKey: ["user", userId],
queryFn: () => getUserById(userId),
staleTime: 60000,
});
};
return (
{users.map((user) => (
handleMouseEnter(user.id)}
>
{user.name}
))}
);
}
```
## Query Error Handling
```typescript
import { useQuery } from "@tanstack/react-query";
import { Alert } from "~/lib/components/ui/alert";
function DataComponent() {
const { data, error, isLoading } = useQuery({
queryKey: ["data"],
queryFn: () => fetchData(),
retry: 1, // Retry once on failure
staleTime: 60000,
});
if (isLoading) return ;
if (error) {
return (
Error loading data
{error.message}
);
}
return ;
}
```
## Key Patterns
### Query States
- `isLoading` - First time loading
- `isFetching` - Background refetch
- `isPending` - No cached data yet
- `isError` - Query failed
- `isSuccess` - Query succeeded
### Mutation States
- `isPending` - Mutation in progress
- `isSuccess` - Mutation succeeded
- `isError` - Mutation failed
### Cache Invalidation
```typescript
// Invalidate all user queries
queryClient.invalidateQueries({ queryKey: ["users"] });
// Invalidate specific user
queryClient.invalidateQueries({ queryKey: ["user", userId] });
// Refetch immediately
queryClient.invalidateQueries({
queryKey: ["users"],
refetchType: "active"
});
```