React Principles logoReact Principles
Scaffoldingv0.1.0

/reactprinciples-query

Scaffold a TanStack Query (React Query) hook following React Principles server-state recipe. Invoke when the user says "create a React Query hook", "fetch data with useQuery", or asks for server state management. Generates the query hook with staleTime, placeholderData, enabled flag where appropriate, plus pairs with a service method and the queryKeys factory. Use for server data only — client state belongs in Zustand.

Allowed tools:ReadWriteGlob

Install

$npx skills add sindev08/react-principles-skills

Installs all skills from the repo. To copy only this skill manually, use the button below.

open_in_newView on GitHub

React Principles — React Query Hook Scaffold

You scaffold a TanStack Query (React Query) hook following the Server State with React Query recipe.

When to invoke

Critical check first

Before generating, confirm with the user that this is server state, not client state.

Inputs needed

Ask the user for:

  1. Hook name — camelCase starting with use (e.g., useUsers, useUser, useSearchUsers)
  2. Query type:
    • List — paginated/filtered list (typically uses staleTime + placeholderData)
    • Detail — single resource by id (typically uses enabled: !!id)
    • Search — debounced search (typically uses enabled: query.length > 0)
    • Mutation — POST/PUT/PATCH/DELETE (uses useMutation + cache invalidation)
  3. Service method — which method on which service (e.g., usersService.getAll, usersService.getById)
  4. Query key — which key from queryKeys factory (e.g., queryKeys.users.list(params))
  5. Locationsrc/features/<feature>/hooks/

What to read first

Read existing hooks for reference:

src/features/examples/hooks/useUsers.ts       # list with staleTime + placeholderData
src/features/examples/hooks/useUser.ts        # detail with enabled
src/features/examples/hooks/useSearchUsers.ts # debounced search
src/features/examples/hooks/useCreateUser.ts  # mutation with invalidation
src/lib/query-keys.ts                          # query keys factory
src/lib/services/users.ts                      # service layer

Confirm queryKeys has the needed entry — if not, instruct the user to add it.

Templates

List query

import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "@/lib/query-keys";
import { <service>, type Get<Resource>Params } from "@/lib/services/<service-file>";

export function use<Resources>(params: Get<Resource>Params = {}) {
  return useQuery({
    queryKey: queryKeys.<resource>.list(params),
    queryFn: () => <service>.getAll(params),
    staleTime: 1000 * 60 * 5,        // 5 minutes
    placeholderData: (prev) => prev, // smooth pagination
  });
}

Detail query

import { useQuery } from "@tanstack/react-query";
import { queryKeys } from "@/lib/query-keys";
import { <service> } from "@/lib/services/<service-file>";

export function use<Resource>(id: string) {
  return useQuery({
    queryKey: queryKeys.<resource>.detail(id),
    queryFn: () => <service>.getById(id),
    enabled: !!id,
    staleTime: 1000 * 60 * 10,       // 10 minutes for stable details
  });
}

Debounced search

import { useQuery } from "@tanstack/react-query";
import { useDebounce } from "@/shared/hooks";
import { queryKeys } from "@/lib/query-keys";
import { <service> } from "@/lib/services/<service-file>";

export function useSearch<Resources>(query: string) {
  const debouncedQuery = useDebounce(query, 300);

  return useQuery({
    queryKey: queryKeys.<resource>.search(debouncedQuery),
    queryFn: () => <service>.search({ q: debouncedQuery }),
    enabled: debouncedQuery.length > 0,
    staleTime: 1000 * 60 * 5,
  });
}

Mutation

import { useMutation, useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/query-keys";
import { <service> } from "@/lib/services/<service-file>";
import type { Create<Resource>Input } from "@/shared/types/<resource>";

export function useCreate<Resource>() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: Create<Resource>Input) => <service>.create(data),
    onSuccess: () => {
      void queryClient.invalidateQueries({ queryKey: queryKeys.<resource>.all });
    },
  });
}

Rules embedded in the templates

  1. Always set staleTime explicitly — defaults are too aggressive for most apps. 5 min for lists, 10 min for details is a sensible baseline.
  2. placeholderData: (prev) => prev for paginated lists — prevents layout shift.
  3. enabled flag for dependent queries — never run a query before its input exists.
  4. Mutations invalidate the relevant list cache via queryClient.invalidateQueries.
  5. Void the invalidatevoid queryClient.invalidateQueries(...) because we don't await it.

After generating

Tell the user:

  1. The file path created
  2. Import path: import { use<Resources> } from "@/features/<feature>/hooks/use<Resources>"
  3. If queryKeys.<resource> doesn't exist yet, instruct them to add it to src/lib/query-keys.ts
  4. If the service method doesn't exist yet, instruct them to add it to the appropriate service file
  5. Suggest pairing with HydrationBoundary + dehydrate for SSR prefetch in Next.js page components

What you should NOT do

Reference

See Server State with React Query recipe and existing hooks in src/features/examples/hooks/.