React Query

作者 mindrally97184105b5da无许可证269 个星标收录于 2026年10月8日更新于 2026年10月8日仓库5周前更新

Guidelines for using React Query for data fetching, caching, and server state synchronization in React applications

AI 生成的概览

关于在 React 应用中使用 React Query(TanStack Query)进行数据获取、缓存与服务器状态同步的指南。

功能
该技能提供在 React 与 TypeScript 项目中使用 React Query 的编码指南和代码示例。内容涵盖 Provider 配置、查询与变更钩子、依赖查询、分页与无限查询、乐观更新、状态管理集成、性能优化、错误处理以及应避免的反模式。产出物是参考指南与代码片段,而非生成的文件或脚本。
适用场景
在实现或审查使用 React Query 的 React 应用中的数据获取、缓存或服务器状态同步时使用。在决定此类代码库中查询钩子、查询键或错误处理的结构时也适用。
运行要求
不包含脚本,仅为说明与代码示例。所述模式假定项目使用 React 与 TypeScript 并安装 react-query 包,可选依赖包括 Axios、Zustand、react-error-boundary 以及提示消息库。

React Query Best Practices

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.

Core Principles

  • Use React Query for all data fetching and caching
  • Leverage React Query's built-in state management instead of useState for server data
  • Use React Context and useReducer for managing client-side global state
  • Avoid excessive API calls through proper caching strategies
  • Always handle loading states and errors properly

Project Structure

src/  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

Setup

Provider Configuration

typescript
// providers/ReactQueryProvider.tsximport { 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>  );}

Query Patterns

Basic Query Hook

typescript
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    }  );}

Query with Error Handling

Services should throw user-friendly errors that React Query can catch and display:

typescript
// services/api/users.tsexport 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 usagefunction 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} />;}

Dependent Queries

typescript
function useUserWithPosts(userId: string) {  const userQuery = useUser(userId);
  const postsQuery = useQuery(    ['posts', userId],    () => fetchUserPosts(userId),    {      enabled: !!userQuery.data,    }  );
  return { userQuery, postsQuery };}

Paginated Queries

typescript
function usePaginatedUsers(page: number, limit: number = 10) {  return useQuery(    ['users', 'list', { page, limit }],    () => fetchUsers({ page, limit }),    {      keepPreviousData: true,    }  );}

Infinite Scroll

typescript
import { useInfiniteQuery } from 'react-query';
function useInfiniteUsers() {  return useInfiniteQuery(    ['users', 'infinite'],    ({ pageParam = 1 }) => fetchUsers({ page: pageParam }),    {      getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,    }  );}

Mutation Patterns

Basic Mutation

typescript
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);    },  });}

Optimistic Updates

typescript
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]);    },  });}

State Management Integration

Combining with Context/Reducer

Use React Query for server state and Context/Reducer for client state:

typescript
// Client state with Contextconst 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 Queryfunction UserDashboard() {  const { theme } = useAppState();         // Client state  const { data: user } = useUser(userId);  // Server state
  return <Dashboard theme={theme} user={user} />;}

Combining with Zustand (Alternative)

typescript
import { create } from 'zustand';
// Client state storeconst useStore = create((set) => ({  theme: 'light',  setTheme: (theme) => set({ theme }),}));
// Component using bothfunction App() {  const theme = useStore((state) => state.theme);  const { data: user } = useUser(userId);
  return <Layout theme={theme} user={user} />;}

Performance Optimization

Query Key Best Practices

typescript
// Structured query keysconst 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,  },};

Selective Subscriptions

typescript
// Only subscribe to user name changesfunction useUserName(userId: string) {  return useUser(userId, {    select: (user) => user.name,  });}

Prefetching

typescript
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>  );}

Error Handling Patterns

Global Error Handler

typescript
const queryClient = new QueryClient({  defaultOptions: {    queries: {      onError: (error: Error) => {        console.error('Query error:', error);      },    },    mutations: {      onError: (error: Error) => {        toast.error(error.message);      },    },  },});

Error Boundaries

typescript
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>  );}

Key Conventions

  1. Use React Query DevTools to inspect cache and track query status
  2. Group react-query hooks within feature-specific directories (feature-based organization)
  3. Always handle errors properly with user-friendly messages and retry options
  4. Fetch only required data - use API parameters to reduce data transfer
  5. Avoid deeply nesting queries - flatten when possible for better performance
  6. Use local state for component-specific data, global state for shared data
  7. Leverage React Query's built-in caching and state management capabilities

Anti-Patterns to Avoid

  • Do not use useEffect for data fetching
  • Do not store server data in useState
  • Do not forget loading and error state handling
  • Do not create queries without proper cache invalidation strategies
  • Do not skip the enabled option for conditional queries
  • Do not ignore TypeScript types for query responses

来源与署名

来源:mindrally/skills位于react-query提交9718410

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架