lovstudio:install-tanstack-query

Installation
SKILL.md

install-tanstack-query — TanStack Query 初始化与重构

Use this skill to add TanStack Query to a project or refactor existing request state into a shared query/mutation layer.

When to Use

  • The user asks to install or initialize TanStack Query / React Query.
  • The project has repeated useEffect + useState + fetch/invoke request code.
  • The user wants all network-backed reads to share cache, refetch, invalidation, and loading/error behavior.
  • A Tauri app uses many invoke() reads that should be treated as server state.
  • The user mentions RTK Query but the project already uses, or prefers, TanStack Query.

Workflow

Step 1: Read Local Rules First

Before changing files, inspect local instructions and project shape:

pwd
find .. -name AGENTS.md -print
rg -n "@tanstack/react-query|react-query|@reduxjs/toolkit|createApi|useQuery|useMutation|fetch\\(|invoke\\(" package.json src app pages components 2>/dev/null

Honor project-specific constraints. If local instructions say not to run build, do not run it. Prefer rg and inspect existing patterns before adding new abstractions.

Step 2: Classify Request Code

Separate code into three buckets:

Bucket Examples TanStack Query?
Server state reads list/get/search/version/status/catalog/settings loaded from network or Tauri backend Yes, useQuery
Server state writes save/delete/toggle/install request, followed by cache changes Yes, useMutation
Imperative side effects terminal I/O, file open, clipboard, app relaunch, installer progress events, streaming channels Usually no

Do not force command-style effects into Query just to make the code look uniform. The goal is unified server state, not hiding every side effect.

Step 3: Install Only If Needed

Check package.json first. If TanStack Query is absent, detect package manager from lockfiles and install:

pnpm add @tanstack/react-query
npm install @tanstack/react-query
yarn add @tanstack/react-query
bun add @tanstack/react-query

Only add persistence or devtools when the project already uses them or the user explicitly asks:

pnpm add @tanstack/react-query-persist-client @tanstack/query-sync-storage-persister
pnpm add -D @tanstack/react-query-devtools

Step 4: Add Provider

Add one top-level QueryClientProvider near the app root. Keep it consistent with the existing architecture.

Recommended defaults:

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: Infinity,
      refetchOnWindowFocus: false,
      retry: false,
    },
  },
});

root.render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
);

Adjust defaults for product needs. Data that changes frequently should use a shorter staleTime, polling, streaming, or explicit invalidation.

Step 5: Create Shared Query Infrastructure

Prefer small local wrappers over scattering raw useQuery calls everywhere.

For Tauri apps:

import { invoke } from "@tauri-apps/api/core";
import {
  useMutation,
  useQuery,
  useQueryClient,
  type QueryKey,
  type UseQueryOptions,
  type UseMutationOptions,
} from "@tanstack/react-query";

type InvokeQueryOptions<TQueryFnData, TData> = Omit<
  UseQueryOptions<TQueryFnData, Error, TData, QueryKey>,
  "queryKey" | "queryFn"
>;

export function useInvokeQuery<TQueryFnData, TData = TQueryFnData>(
  queryKey: QueryKey,
  command: string,
  args?: Record<string, unknown>,
  options?: InvokeQueryOptions<TQueryFnData, TData>,
) {
  return useQuery<TQueryFnData, Error, TData, QueryKey>({
    queryKey,
    queryFn: () => invoke<TQueryFnData>(command, args),
    staleTime: Infinity,
    refetchOnMount: false,
    refetchOnWindowFocus: false,
    ...options,
  });
}

type InvokeMutationOptions<T, V> = Omit<UseMutationOptions<T, Error, V>, "mutationFn">;

export function useInvokeMutation<T, V = void>(
  command: string,
  invalidateKeys?: QueryKey[],
  options?: InvokeMutationOptions<T, V>,
) {
  const queryClient = useQueryClient();
  return useMutation<T, Error, V>({
    mutationFn: (variables) => invoke<T>(command, variables as Record<string, unknown>),
    ...options,
    onSuccess: (data, variables, context, mutation) => {
      options?.onSuccess?.(data, variables, context, mutation);
      invalidateKeys?.forEach((key) => {
        queryClient.invalidateQueries({ queryKey: key });
      });
    },
  });
}

Add stable query keys:

export const queryKeys = {
  projects: ["projects"] as const,
  settings: ["settings"] as const,
};

For REST apps, use the same structure but wrap the project's API client instead of Tauri invoke().

Step 6: Refactor Incrementally

Start with duplicated and user-visible requests:

  1. Replace manual read state:
const { data = [], isLoading, error, refetch } = useInvokeQuery<Item[]>(
  queryKeys.items,
  "list_items",
);
  1. Replace write requests:
const saveItem = useInvokeMutation<Item, { item: Item }>(
  "save_item",
  [queryKeys.items],
);
  1. Use queryClient.setQueryData() for optimistic toggles when the UX needs instant feedback.

  2. Keep local UI state local. Dialog open state, form drafts, selected tabs, and filters usually do not belong in TanStack Query.

  3. Do not break existing streaming subscriptions. If a stream already pushes data into queryClient.setQueryData(), keep that pattern.

Step 7: Verification

Run the lightest reliable checks allowed by the repo:

pnpm exec tsc --noEmit --pretty false
npm run typecheck
yarn typecheck
bun run typecheck

Do not run heavy builds or dev server commands if local instructions forbid them. For UI-heavy changes, suggest browser verification or screenshots after type checks pass.

Final Response Checklist

Report:

  • What query provider/wrappers/keys were added or reused.
  • Which request flows moved to TanStack Query.
  • Which imperative flows intentionally stayed command-driven.
  • Which checks were run and whether any warnings remain.
Related skills
Installs
3
First Seen
1 day ago