react-query by mindrally/skills
npx skills add https://github.com/mindrally/skills --skill react-query你是一位精通 React Query、TypeScript 和 React 开发的专家。React Query(现为 TanStack Query)通过内置的缓存、后台更新和过期数据管理,简化了数据获取逻辑。
useStateuseReducer 来管理客户端全局状态src/
components/
[Feature]/
index.tsx
queries.ts # 特定功能的查询钩子
mutations.ts # 特定功能的变更钩子
hooks/
useAuth.ts
useApi.ts
services/
api/
client.ts # Axios/fetch 配置
users.ts # 用户 API 函数
posts.ts # 帖子 API 函数
providers/
ReactQueryProvider.tsx
types/
index.ts
// providers/ReactQueryProvider.tsx
import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 分钟
cacheTime: 30 * 60 * 1000, // 30 分钟
retry: 2,
refetchOnWindowFocus: true,
},
},
});
export function ReactQueryProvider({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
);
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
import { useQuery } from 'react-query';
import { fetchUser, User } from '@/services/api/users';
export function useUser(userId: string) {
return useQuery<User, Error>(
['user', userId],
() => fetchUser(userId),
{
enabled: !!userId,
staleTime: 1000 * 60 * 10, // 10 分钟
}
);
}
服务应抛出用户友好的错误,以便 React Query 捕获并显示:
// services/api/users.ts
export async function fetchUser(userId: string): Promise<User> {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
// 抛出用户友好的错误信息
throw new Error('无法加载用户资料。请重试。');
}
return response.json();
}
// 组件使用
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useUser(userId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={error.message} />;
return <ProfileCard user={user} />;
}
function useUserWithPosts(userId: string) {
const userQuery = useUser(userId);
const postsQuery = useQuery(
['posts', userId],
() => fetchUserPosts(userId),
{
enabled: !!userQuery.data,
}
);
return { userQuery, postsQuery };
}
function usePaginatedUsers(page: number, limit: number = 10) {
return useQuery(
['users', 'list', { page, limit }],
() => fetchUsers({ page, limit }),
{
keepPreviousData: true,
}
);
}
import { useInfiniteQuery } from 'react-query';
function useInfiniteUsers() {
return useInfiniteQuery(
['users', 'infinite'],
({ pageParam = 1 }) => fetchUsers({ page: pageParam }),
{
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
}
);
}
import { useMutation, useQueryClient } from 'react-query';
function useCreateUser() {
const queryClient = useQueryClient();
return useMutation(createUser, {
onSuccess: () => {
queryClient.invalidateQueries(['users']);
},
onError: (error: Error) => {
toast.error(error.message);
},
});
}
function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation(updateUser, {
onMutate: async (updatedUser) => {
await queryClient.cancelQueries(['user', updatedUser.id]);
const previousUser = queryClient.getQueryData(['user', updatedUser.id]);
queryClient.setQueryData(['user', updatedUser.id], updatedUser);
return { previousUser };
},
onError: (err, updatedUser, context) => {
if (context?.previousUser) {
queryClient.setQueryData(['user', updatedUser.id], context.previousUser);
}
},
onSettled: (data, error, updatedUser) => {
queryClient.invalidateQueries(['user', updatedUser.id]);
},
});
}
使用 React Query 处理服务器状态,使用 Context/Reducer 处理客户端状态:
// 使用 Context 的客户端状态
const AppStateContext = createContext<AppState | undefined>(undefined);
const AppDispatchContext = createContext<Dispatch<Action> | undefined>(undefined);
function AppProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
}
// 使用 React Query 的服务器状态
function UserDashboard() {
const { theme } = useAppState(); // 客户端状态
const { data: user } = useUser(userId); // 服务器状态
return <Dashboard theme={theme} user={user} />;
}
import { create } from 'zustand';
// 客户端状态存储
const useStore = create((set) => ({
theme: 'light',
setTheme: (theme) => set({ theme }),
}));
// 同时使用两者的组件
function App() {
const theme = useStore((state) => state.theme);
const { data: user } = useUser(userId);
return <Layout theme={theme} user={user} />;
}
// 结构化的查询键
const queryKeys = {
users: {
all: ['users'] as const,
lists: () => [...queryKeys.users.all, 'list'] as const,
list: (filters: Filters) => [...queryKeys.users.lists(), filters] as const,
details: () => [...queryKeys.users.all, 'detail'] as const,
detail: (id: string) => [...queryKeys.users.details(), id] as const,
},
};
// 仅订阅用户名变更
function useUserName(userId: string) {
return useUser(userId, {
select: (user) => user.name,
});
}
function UserListItem({ userId }: { userId: string }) {
const queryClient = useQueryClient();
const handleMouseEnter = () => {
queryClient.prefetchQuery(
['user', userId],
() => fetchUser(userId),
{ staleTime: 60000 }
);
};
return (
<li onMouseEnter={handleMouseEnter}>
<Link to={`/users/${userId}`}>查看资料</Link>
</li>
);
}
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: (error: Error) => {
console.error('查询错误:', error);
},
},
mutations: {
onError: (error: Error) => {
toast.error(error.message);
},
},
},
});
import { QueryErrorResetBoundary } from 'react-query';
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
<p>出错了: {error.message}</p>
<button onClick={resetErrorBoundary}>重试</button>
</div>
)}
>
<UserProfile />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}
useEffect 来获取数据useState 中enabled 选项每周安装量
264
代码仓库
GitHub 星标数
43
首次出现
2026年1月25日
安全审计
安装于
opencode230
codex227
gemini-cli225
github-copilot214
amp197
kimi-cli194
You are an expert in React Query, TypeScript, and React development. React Query (now TanStack Query) simplifies data fetching logic with built-in caching, background updates, and stale data management.
useState for server datauseReducer for managing client-side global statesrc/
components/
[Feature]/
index.tsx
queries.ts # Feature-specific query hooks
mutations.ts # Feature-specific mutation hooks
hooks/
useAuth.ts
useApi.ts
services/
api/
client.ts # Axios/fetch configuration
users.ts # User API functions
posts.ts # Post API functions
providers/
ReactQueryProvider.tsx
types/
index.ts
// providers/ReactQueryProvider.tsx
import { QueryClient, QueryClientProvider } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 30 * 60 * 1000, // 30 minutes
retry: 2,
refetchOnWindowFocus: true,
},
},
});
export function ReactQueryProvider({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
);
}
import { useQuery } from 'react-query';
import { fetchUser, User } from '@/services/api/users';
export function useUser(userId: string) {
return useQuery<User, Error>(
['user', userId],
() => fetchUser(userId),
{
enabled: !!userId,
staleTime: 1000 * 60 * 10, // 10 minutes
}
);
}
Services should throw user-friendly errors that React Query can catch and display:
// services/api/users.ts
export async function fetchUser(userId: string): Promise<User> {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
// Throw user-friendly error message
throw new Error('Unable to load user profile. Please try again.');
}
return response.json();
}
// Component usage
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useUser(userId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage message={error.message} />;
return <ProfileCard user={user} />;
}
function useUserWithPosts(userId: string) {
const userQuery = useUser(userId);
const postsQuery = useQuery(
['posts', userId],
() => fetchUserPosts(userId),
{
enabled: !!userQuery.data,
}
);
return { userQuery, postsQuery };
}
function usePaginatedUsers(page: number, limit: number = 10) {
return useQuery(
['users', 'list', { page, limit }],
() => fetchUsers({ page, limit }),
{
keepPreviousData: true,
}
);
}
import { useInfiniteQuery } from 'react-query';
function useInfiniteUsers() {
return useInfiniteQuery(
['users', 'infinite'],
({ pageParam = 1 }) => fetchUsers({ page: pageParam }),
{
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
}
);
}
import { useMutation, useQueryClient } from 'react-query';
function useCreateUser() {
const queryClient = useQueryClient();
return useMutation(createUser, {
onSuccess: () => {
queryClient.invalidateQueries(['users']);
},
onError: (error: Error) => {
toast.error(error.message);
},
});
}
function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation(updateUser, {
onMutate: async (updatedUser) => {
await queryClient.cancelQueries(['user', updatedUser.id]);
const previousUser = queryClient.getQueryData(['user', updatedUser.id]);
queryClient.setQueryData(['user', updatedUser.id], updatedUser);
return { previousUser };
},
onError: (err, updatedUser, context) => {
if (context?.previousUser) {
queryClient.setQueryData(['user', updatedUser.id], context.previousUser);
}
},
onSettled: (data, error, updatedUser) => {
queryClient.invalidateQueries(['user', updatedUser.id]);
},
});
}
Use React Query for server state and Context/Reducer for client state:
// Client state with Context
const AppStateContext = createContext<AppState | undefined>(undefined);
const AppDispatchContext = createContext<Dispatch<Action> | undefined>(undefined);
function AppProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppStateContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
}
// Server state with React Query
function UserDashboard() {
const { theme } = useAppState(); // Client state
const { data: user } = useUser(userId); // Server state
return <Dashboard theme={theme} user={user} />;
}
import { create } from 'zustand';
// Client state store
const useStore = create((set) => ({
theme: 'light',
setTheme: (theme) => set({ theme }),
}));
// Component using both
function App() {
const theme = useStore((state) => state.theme);
const { data: user } = useUser(userId);
return <Layout theme={theme} user={user} />;
}
// Structured query keys
const queryKeys = {
users: {
all: ['users'] as const,
lists: () => [...queryKeys.users.all, 'list'] as const,
list: (filters: Filters) => [...queryKeys.users.lists(), filters] as const,
details: () => [...queryKeys.users.all, 'detail'] as const,
detail: (id: string) => [...queryKeys.users.details(), id] as const,
},
};
// Only subscribe to user name changes
function useUserName(userId: string) {
return useUser(userId, {
select: (user) => user.name,
});
}
function UserListItem({ userId }: { userId: string }) {
const queryClient = useQueryClient();
const handleMouseEnter = () => {
queryClient.prefetchQuery(
['user', userId],
() => fetchUser(userId),
{ staleTime: 60000 }
);
};
return (
<li onMouseEnter={handleMouseEnter}>
<Link to={`/users/${userId}`}>View Profile</Link>
</li>
);
}
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onError: (error: Error) => {
console.error('Query error:', error);
},
},
mutations: {
onError: (error: Error) => {
toast.error(error.message);
},
},
},
});
import { QueryErrorResetBoundary } from 'react-query';
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
<p>Something went wrong: {error.message}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)}
>
<UserProfile />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}
useEffect for data fetchinguseStateenabled option for conditional queriesWeekly Installs
264
Repository
GitHub Stars
43
First Seen
Jan 25, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode230
codex227
gemini-cli225
github-copilot214
amp197
kimi-cli194
Flutter应用架构设计指南:分层结构、数据层实现与最佳实践
3,400 周安装