一键导入
react-query-patterns
React Query server state management patterns. Activated when applying Query Factory, queryOptions, or mutationOptions patterns.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
React Query server state management patterns. Activated when applying Query Factory, queryOptions, or mutationOptions patterns.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
AGENTS.md authoring patterns. Activated when writing AI agent context files, working with agents.md or AGENTS.md.
Codebase onboarding guide. Activated when starting a new project analysis, requesting systematic codebase understanding, or running /onboard.
Feature-Sliced Design architecture patterns. Activated when working with FSD layers, slices, dependency rules, or structuring frontend code.
Modern React 19+ patterns. Activated when working with state management, Suspense, Compound Components, or React hooks.
Practical TypeScript patterns. Activated when working with type inference, utility types, generics, type guards, or type narrowing.
Writing readable, maintainable code. Activated when applying naming conventions, function decomposition, or Early Return patterns.
基于 SOC 职业分类
| name | react-query-patterns |
| description | React Query server state management patterns. Activated when applying Query Factory, queryOptions, or mutationOptions 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: 'john@example.com' });
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());