Data Fetching
Server Actions with React Query for optimal data management
Architecture Overview
Our data fetching strategy combines Server Actions with React Query for a powerful, type-safe approach:
- Server Actions handle all API communication (authentication, API client setup)
- React Query hooks manage client-side caching, refetching, and state
- Centralized query keys ensure consistent cache invalidation
Fetching Strategies
"use client";
import { useQuery } from "@tanstack/react-query";
import { getGuestDisruptions } from "@/app/actions/guest-disruptions/get-guest-disruptions";
import { queryKeys } from "@/lib/query-keys";
export function useGuestDisruptions(scheduleId: string, params?: GuestDisruptionsParams) {
return useQuery({
queryKey: queryKeys.guestDisruptions.list(scheduleId, params),
queryFn: () => getGuestDisruptions(scheduleId, params),
staleTime: 5 * 60 * 1000, // 5 min
gcTime: 15 * 60 * 1000, // 15 min
refetchOnWindowFocus: false,
enabled: !!scheduleId,
placeholderData: (previousData) => previousData, // Smooth UX
});
}Recommended approach - Use custom hooks that wrap Server Actions with React Query.
Benefits:
- Automatic caching and background refetching
- Optimistic updates and request deduplication
- Consistent configuration across the app
- Type-safe with full TypeScript support
"use server";
import { auth } from "@clerk/nextjs/server";
import { createApiClient } from "@/lib/api-client";
import { API_CONFIG } from "@/config/api";
export async function getGuestDisruptions(
scheduleId: string,
params?: GuestDisruptionsParams
) {
// Get authentication token
const { getToken } = await auth();
const token = await getToken();
if (!token) {
throw new Error("Unauthorized");
}
// Create authenticated API client
const apiClient = createApiClient(token);
// Build query parameters
const searchParams = new URLSearchParams();
if (params?.pathwayAvailability !== undefined) {
searchParams.append("pathwayAvailability", params.pathwayAvailability.toString());
}
// ... other params
const endpoint = API_CONFIG.endpoints.guestDisruptions.list(scheduleId);
const data = await apiClient.get(`${endpoint}?${searchParams}`).json();
return data;
}Server Actions handle authentication, API client setup, and error handling.
Responsibilities:
- Clerk authentication
- API client configuration with tokens
- Request/response mapping
- Error handling
"use client";
import { useGuestDisruptions } from "@/hooks/guest-disruptions/use-guest-disruptions";
export function GuestDisruptionsTable({ scheduleId }: Props) {
const { data, isLoading, error } = useGuestDisruptions(scheduleId);
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
return <DataTable data={data.disruptions} columns={columns} />;
}Use the custom hooks directly in components for clean, declarative code.
React Query Setup
Provider Configuration
"use client";
import { useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
export function QueryProvider({ children }: { children: ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 2 * 60 * 1000, // 2 minutes
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
retry: 1,
refetchOnWindowFocus: false,
},
},
})
);
return (
<QueryClientProvider client={queryClient}>
{children}
{process.env.NODE_ENV === "development" && <ReactQueryDevtools />}
</QueryClientProvider>
);
}Centralized Query Keys
export const queryKeys = {
// Schedules
all: ["schedules"] as const,
lists: () => [...queryKeys.all, "list"] as const,
list: () => [...queryKeys.lists()] as const,
details: () => [...queryKeys.all, "detail"] as const,
detail: (id: string) => [...queryKeys.details(), id] as const,
// Guest Disruptions
guestDisruptions: {
all: ["guestDisruptions"] as const,
lists: () => [...queryKeys.guestDisruptions.all, "list"] as const,
list: (scheduleId: string, params?: Record<string, unknown>) =>
[...queryKeys.guestDisruptions.lists(), scheduleId, params] as const,
filterOptions: (scheduleId: string) =>
[...queryKeys.guestDisruptions.all, "filterOptions", scheduleId] as const,
scheduleChanges: (scheduleId: string, route: string) =>
[
...queryKeys.guestDisruptions.all,
"scheduleChanges",
scheduleId,
route,
] as const,
impactedDates: (scheduleId: string, route: string) =>
[
...queryKeys.guestDisruptions.all,
"impactedDates",
scheduleId,
route,
] as const,
alternativePathways: (
scheduleId: string,
route: string,
params?: Record<string, unknown>
) =>
[
...queryKeys.guestDisruptions.all,
"alternativePathways",
scheduleId,
route,
params,
] as const,
},
} as constCustom Hook Pattern
"use client"
import { useQuery } from "@tanstack/react-query"
import { queryKeys } from "@/lib/query-keys"
import { getSchedules } from "@/app/actions/schedules/get-schedules"
export function useSchedules() {
return useQuery({
queryKey: queryKeys.list(),
queryFn: getSchedules,
// Uses default staleTime (2 min) and gcTime (10 min)
})
}Best Practices
Next: Learn about Routing with Next.js App Router.