From common-skills
React Query server state management patterns. Activated when applying Query Factory, queryOptions, or mutationOptions patterns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/common-skills:react-query-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Server state management - Query Factory & Options patterns
Server state management - Query Factory & Options patterns
entities/user/api/
├── keys.ts # Query Key Factory
├── get-user.ts # Individual API functions
├── get-users.ts
├── create-user.ts
├── update-user.ts
├── query.ts # queryOptions definitions
└── mutation.ts # mutationOptions definitions
All Query Keys managed via Factory pattern.
// entities/user/api/keys.ts
export const userKeys = {
all: ['users'] as const,
lists: () => [...userKeys.all, 'list'] as const,
list: (filters: UserFilters) => [...userKeys.lists(), filters] as const,
details: () => [...userKeys.all, 'detail'] as const,
detail: (id: string) => [...userKeys.details(), id] as const,
};
Cache invalidation:
queryClient.invalidateQueries({ queryKey: userKeys.detail(userId) }); // specific user
queryClient.invalidateQueries({ queryKey: userKeys.details() }); // all details
queryClient.invalidateQueries({ queryKey: userKeys.lists() }); // all lists
queryClient.invalidateQueries({ queryKey: userKeys.all }); // everything
Separate API functions into individual files.
// entities/user/api/get-user.ts
import { api } from '@/shared/api';
import type { User } from '../model/types';
export async function getUser(id: string): Promise<User> {
const response = await api.get<User>(`/users/${id}`);
return response.data;
}
All Queries defined via queryOptions.
// entities/user/api/query.ts
import { queryOptions } from '@tanstack/react-query';
import { userKeys } from './keys';
import { getUser } from './get-user';
import { getUsers } from './get-users';
export const userQueries = {
all: () => queryOptions({
queryKey: userKeys.all,
}),
list: (filters?: UserFilters) => queryOptions({
queryKey: userKeys.list(filters ?? {}),
queryFn: () => getUsers(filters),
}),
detail: (id: string) => queryOptions({
queryKey: userKeys.detail(id),
queryFn: () => getUser(id),
staleTime: 5 * 60 * 1000,
}),
};
Usage:
const { data } = useQuery(userQueries.detail(userId));
const { data } = useSuspenseQuery(userQueries.detail(userId));
await queryClient.prefetchQuery(userQueries.detail(userId));
const user = await queryClient.ensureQueryData(userQueries.detail(userId));
All Mutations defined via mutationOptions.
// entities/user/api/mutation.ts
import { mutationOptions } from '@tanstack/react-query';
import { userKeys } from './keys';
import { createUser } from './create-user';
import { updateUser } from './update-user';
export const userMutations = {
create: () => mutationOptions({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
},
}),
update: () => mutationOptions({
mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) =>
updateUser(id, data),
onSuccess: (_, { id }) => {
queryClient.invalidateQueries({ queryKey: userKeys.detail(id) });
queryClient.invalidateQueries({ queryKey: userKeys.lists() });
},
}),
};
Usage:
const createUserMutation = useMutation(userMutations.create());
createUserMutation.mutate({ name: 'John', email: '[email protected]' });
export const userMutations = {
update: () => mutationOptions({
mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) =>
updateUser(id, data),
onMutate: async ({ id, data }) => {
await queryClient.cancelQueries({ queryKey: userKeys.detail(id) });
const previousUser = queryClient.getQueryData(userKeys.detail(id));
queryClient.setQueryData(userKeys.detail(id), (old: User) => ({
...old,
...data,
}));
return { previousUser };
},
onError: (err, { id }, context) => {
if (context?.previousUser) {
queryClient.setQueryData(userKeys.detail(id), context.previousUser);
}
},
onSettled: (_, __, { id }) => {
queryClient.invalidateQueries({ queryKey: userKeys.detail(id) });
},
}),
};
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Loading />}>
<UserProfile userId={userId} />
</Suspense>
</ErrorBoundary>
function UserProfile({ userId }: { userId: string }) {
const { data } = useSuspenseQuery(userQueries.detail(userId));
return <div>{data.name}</div>; // data is always defined
}
| Data Type | staleTime | Notes |
|---|---|---|
| Rarely changing | Infinity | Manual invalidation only |
| Standard | 5 * 60 * 1000 | 5 min |
| Real-time | 0 + refetchInterval | Polling |
Global defaults:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
retry: 1,
throwOnError: true,
},
},
});
entities/user/
├── api/
│ ├── index.ts # re-export
│ ├── keys.ts # Query Key Factory
│ ├── get-user.ts
│ ├── get-users.ts
│ ├── create-user.ts
│ ├── update-user.ts
│ ├── delete-user.ts
│ ├── query.ts # queryOptions
│ └── mutation.ts # mutationOptions
├── model/
│ └── types.ts
└── index.ts # Public API
// AVOID: inline queryKey
useQuery({ queryKey: ['user', userId] });
// USE: Query Key Factory
useQuery(userQueries.detail(userId));
// AVOID: inline API call in queryFn
useQuery({
queryKey: userKeys.detail(id),
queryFn: () => api.get(`/users/${id}`),
});
// USE: separated API function via queryOptions
useQuery(userQueries.detail(id));
// AVOID: inline mutation definition
useMutation({ mutationFn: (data) => api.post('/users', data) });
// USE: mutationOptions
useMutation(userMutations.create());
// AVOID: conditional logic in queryFn
useQuery({ queryFn: () => (isAdmin ? fetchAdminData() : fetchUserData()) });
// USE: separate queryOptions
useQuery(isAdmin ? adminQueries.data() : userQueries.data());
npx claudepluginhub dding-g/ddingg-claude-marketplace --plugin common-skillsProvides TanStack Query v5 reference for React data fetching, caching, server state management using useQuery/useMutation hooks, QueryClient setup, optimistic updates, Next.js SSR/hydration, testing, TypeScript, and advanced patterns.
Manages server state with automatic caching, background refetching, pagination, infinite scrolling, and optimistic updates for React, Vue, Svelte, Angular apps.
Expert TanStack Query guidance for React and Next.js apps covering asynchronous state management, data fetching, caching, mutations, optimistic updates, and SSR integration.