# Advanced TanStack Query Patterns Complete examples for dependent queries, parallel queries, and custom hooks. ## Dependent Queries ```typescript function UserOrganization({ userId }: { userId: string }) { // First query: get user const { data: user } = useQuery({ queryKey: ["user", userId], queryFn: () => getUserById(userId), }); // Second query: get organization (depends on user) const { data: organization } = useQuery({ queryKey: ["organization", user?.organization_id], queryFn: () => getOrganizationById(user!.organization_id), enabled: !!user?.organization_id, // Only run if user exists }); if (!user) return ; return (

{user.name}

{organization &&

Organization: {organization.name}

}
); } ``` ## Parallel Queries ```typescript function Dashboard() { // Run multiple queries in parallel const userQuery = useQuery({ queryKey: ["user", "current"], queryFn: () => getCurrentUser(), }); const statsQuery = useQuery({ queryKey: ["stats", "dashboard"], queryFn: () => getDashboardStats(), }); const recentQuery = useQuery({ queryKey: ["recent", "activity"], queryFn: () => getRecentActivity(), }); // All queries run simultaneously (parallel fetching) const isLoading = userQuery.isLoading || statsQuery.isLoading || recentQuery.isLoading; if (isLoading) return ; return (
); } ``` ## Custom Query Hooks ```typescript // src/lib/hooks/use-user.ts import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { getUserById, updateUser } from "~/lib/server/functions/users"; export function useUser(userId: string) { const queryClient = useQueryClient(); const query = useQuery({ queryKey: ["user", userId], queryFn: () => getUserById(userId), staleTime: 60000, }); const updateMutation = useMutation({ mutationFn: (data: UserUpdate) => updateUser(userId, data), onSuccess: (updatedUser) => { queryClient.setQueryData(["user", userId], updatedUser); }, }); return { user: query.data, isLoading: query.isLoading, error: query.error, update: updateMutation.mutate, isUpdating: updateMutation.isPending, }; } ``` ```typescript // Using custom hook function UserProfile({ userId }: { userId: string }) { const { user, isLoading, update, isUpdating } = useUser(userId); if (isLoading) return ; return (

{user.name}

); } ``` ## Query Composition ```typescript // Base hook for fetching user function useUserQuery(userId: string) { return useQuery({ queryKey: ["user", userId], queryFn: () => getUserById(userId), staleTime: 60000, }); } // Composed hook with additional functionality function useUserWithPermissions(userId: string) { const userQuery = useUserQuery(userId); const permissionsQuery = useQuery({ queryKey: ["user", userId, "permissions"], queryFn: () => getUserPermissions(userId), enabled: !!userQuery.data, staleTime: 60000, }); return { user: userQuery.data, permissions: permissionsQuery.data, isLoading: userQuery.isLoading || permissionsQuery.isLoading, error: userQuery.error || permissionsQuery.error, }; } ``` ## Background Refetching ```typescript function RealtimeNotifications() { const { data: notifications } = useQuery({ queryKey: ["notifications"], queryFn: () => getNotifications(), staleTime: 0, // Always stale refetchInterval: 30000, // Refetch every 30 seconds refetchIntervalInBackground: true, // Even when tab is not focused }); return (
{notifications?.map((notif) => ( ))}
); } ``` ## Suspense Mode ```typescript import { Suspense } from "react"; import { useSuspenseQuery } from "@tanstack/react-query"; function UserProfile({ userId }: { userId: string }) { // Suspense mode - no loading state needed const { data: user } = useSuspenseQuery({ queryKey: ["user", userId], queryFn: () => getUserById(userId), }); return
{user.name}
; } function App() { return ( }> ); } ``` ## Placeholders and Initial Data ```typescript function UserProfile({ userId }: { userId: string }) { const queryClient = useQueryClient(); const { data: user } = useQuery({ queryKey: ["user", userId], queryFn: () => getUserById(userId), // Placeholder data while loading placeholderData: () => { // Try to find user in list cache const users = queryClient.getQueryData(["users"]); return users?.find(u => u.id === userId); }, staleTime: 60000, }); return
{user?.name}
; } ``` ## Query Cancellation ```typescript import { useQuery } from "@tanstack/react-query"; function SearchResults({ query }: { query: string }) { const { data, isLoading } = useQuery({ queryKey: ["search", query], queryFn: async ({ signal }) => { // Pass AbortSignal to fetch const response = await fetch(`/api/search?q=${query}`, { signal }); return response.json(); }, staleTime: 60000, // Query automatically cancelled when query key changes }); return
...
; } ``` ## Key Patterns ### When to Use Custom Hooks - Reusing query logic across components - Combining multiple queries - Adding business logic to queries - Simplifying component code ### When to Use Dependent Queries - Second query needs data from first query - Use `enabled` option to control execution ### When to Use Parallel Queries - Multiple independent data sources - No dependencies between queries - Want to show loading state for all together ### When to Use Suspense - React 18+ with Suspense boundaries - Want declarative loading states - Component tree can suspend ### Performance Tips - Use `placeholderData` for instant UI feedback - Use `staleTime` to reduce unnecessary refetches - Use `refetchInterval` for real-time updates - Cancel queries when component unmounts (automatic)