tanstack-query

TanStack Query Best Practices

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "tanstack-query" with this command: npx skills add alicoder001/agent-skills/alicoder001-agent-skills-tanstack-query

TanStack Query Best Practices

Server state management with automatic caching and synchronization.

Instructions

  1. Setup

// providers/QueryProvider.tsx import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 minutes gcTime: 1000 * 60 * 30, // 30 minutes (formerly cacheTime) retry: 1, refetchOnWindowFocus: false, }, }, });

export function QueryProvider({ children }: { children: React.ReactNode }) { return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ); }

  1. Query Keys Factory

// lib/queryKeys.ts export const queryKeys = { users: { all: ['users'] as const, list: (filters: UserFilters) => [...queryKeys.users.all, 'list', filters] as const, detail: (id: string) => [...queryKeys.users.all, 'detail', id] as const, }, posts: { all: ['posts'] as const, list: (filters: PostFilters) => [...queryKeys.posts.all, 'list', filters] as const, detail: (id: string) => [...queryKeys.posts.all, 'detail', id] as const, byUser: (userId: string) => [...queryKeys.posts.all, 'user', userId] as const, }, } as const;

  1. Custom Query Hook

// hooks/useUsers.ts import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { queryKeys } from '@/lib/queryKeys'; import { userApi } from '@/lib/api';

export function useUsers(filters?: UserFilters) { return useQuery({ queryKey: queryKeys.users.list(filters ?? {}), queryFn: () => userApi.getAll(filters), }); }

export function useUser(id: string) { return useQuery({ queryKey: queryKeys.users.detail(id), queryFn: () => userApi.getById(id), enabled: !!id, // Only run when id exists }); }

  1. Mutations

// hooks/useCreateUser.ts export function useCreateUser() { const queryClient = useQueryClient();

return useMutation({ mutationFn: (data: CreateUserDto) => userApi.create(data), onSuccess: () => { // Invalidate and refetch queryClient.invalidateQueries({ queryKey: queryKeys.users.all }); }, onError: (error) => { toast.error(error.message); }, }); }

// Usage function CreateUserForm() { const { mutate, isPending } = useCreateUser();

const handleSubmit = (data: CreateUserDto) => { mutate(data, { onSuccess: () => toast.success('User created!'), }); }; }

  1. Optimistic Updates

export function useUpdateUser() { const queryClient = useQueryClient();

return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) => userApi.update(id, data), onMutate: async ({ id, data }) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: queryKeys.users.detail(id) });

  // Snapshot previous value
  const previousUser = queryClient.getQueryData(queryKeys.users.detail(id));

  // Optimistically update
  queryClient.setQueryData(queryKeys.users.detail(id), (old: User) => ({
    ...old,
    ...data,
  }));

  return { previousUser };
},
onError: (err, { id }, context) => {
  // Rollback on error
  queryClient.setQueryData(
    queryKeys.users.detail(id),
    context?.previousUser
  );
},
onSettled: (_, __, { id }) => {
  // Refetch after mutation
  queryClient.invalidateQueries({ queryKey: queryKeys.users.detail(id) });
},

}); }

  1. Infinite Query (Pagination)

export function useInfinitePosts() { return useInfiniteQuery({ queryKey: queryKeys.posts.all, queryFn: ({ pageParam = 1 }) => postApi.getAll({ page: pageParam }), getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined, initialPageParam: 1, }); }

// Usage function PostList() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, } = useInfinitePosts();

return ( <> {data?.pages.map((page) => page.posts.map((post) => <PostCard key={post.id} post={post} />) )} {hasNextPage && ( <button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}> {isFetchingNextPage ? 'Loading...' : 'Load More'} </button> )} </> ); }

  1. Prefetching

// Prefetch on hover function UserLink({ userId }: { userId: string }) { const queryClient = useQueryClient();

const prefetchUser = () => { queryClient.prefetchQuery({ queryKey: queryKeys.users.detail(userId), queryFn: () => userApi.getById(userId), staleTime: 1000 * 60 * 5, }); };

return ( <Link href={/users/${userId}} onMouseEnter={prefetchUser}> View User </Link> ); }

  1. Dependent Queries

function useUserPosts(userId: string | undefined) { const userQuery = useUser(userId!);

return useQuery({ queryKey: queryKeys.posts.byUser(userId!), queryFn: () => postApi.getByUser(userId!), enabled: !!userId && userQuery.isSuccess, }); }

Common Patterns

Pattern Usage

staleTime

How long data stays fresh

gcTime

How long unused data stays in cache

enabled

Conditional fetching

select

Transform response data

placeholderData

Show while loading

References

  • TanStack Query Docs

  • Practical React Query

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

solid

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

reasoning

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

collaboration

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

find-skills

No summary provided by upstream source.

Repository SourceNeeds Review