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 從公開儲存庫中收錄這些內容。

檢舉或申請下架