Server State Management (React Query)
Relevant source files
The following files were used as context for generating this wiki page:The project utilizes TanStack React Query to manage server state, handling data fetching, caching, and synchronization with the backend API. This approach separates server state from global client state (managed by Zustand), providing a robust mechanism for handling asynchronous data operations, as outlined in the project's architecture principles.
React Query is integrated through a system of custom hooks, a centralized query key factory, and a provider-based setup. This allows components to declaratively fetch and mutate data, while React Query handles the complexities of caching, background refetching, and state updates.
Sources: README.md:43-45
1. Configuration and Setup
React Query is configured at the root of the application within the Providers component. This setup involves instantiating a QueryClient and wrapping the application with QueryClientProvider. The ReactQueryDevtools component is also included for debugging purposes during development.
Sources: app/providers.tsx:9-25
The following diagram illustrates the provider setup flow:
Sources: app/providers.tsx:9-25, app/layout.tsx:28-34
1.1. Core Dependencies
The project relies on the following TanStack Query packages, as defined in package.json.
| Package | Version | Description |
|---|---|---|
@tanstack/react-query | ^5.90.7 | Core library for React Query. |
@tanstack/react-query-devtools | ^5.90.2 | Provides devtools for debugging queries. |
@tanstack/eslint-plugin-query | ^5.91.2 | ESLint plugin for enforcing best practices. |
Sources: package.json:44-45, package.json:92
1.2. Provider Implementation
The QueryClient is created once and provided to the entire component tree. This ensures a single cache is used throughout the application.
// app/providers.tsx
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { useState, type ReactNode } from "react";
export function Providers({ children }: { children: ReactNode }): JSX.Element {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
},
},
}),
);
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
A global staleTime of 1 minute is configured, meaning fetched data will be considered fresh for one minute before it is refetched in the background on subsequent requests. This default can be overridden in individual hooks.
Sources: app/providers.tsx:13-15
2. Query Keys Management
A centralized queryKeys object is used to generate consistent and unique keys for all queries. This is crucial for caching, refetching, and invalidating data. The keys are structured hierarchically by feature.
Sources: lib/query-keys.ts
The structure of the queryKeys object is as follows:
This diagram shows the hierarchical structure for products and orders query keys.
Sources: lib/query-keys.ts:8-33
2.1. Key Factory Implementation
The query-keys.ts file exports an object containing functions that return query key arrays.
- List keys:
lists()returns a base key for all lists of a feature (e.g.,['products', 'list']). This is used for broad invalidation. - Filtered list keys:
list(params)returns a specific key for a filtered/paginated list (e.g.,['products', 'list', { page: 1 }]). - Detail keys:
detail(id)returns a key for a single entity (e.g.,['products', 'detail', 'prod-123']).
// lib/query-keys.ts:8-16
export const queryKeys = {
products: {
all: ["products"] as const,
lists: () => [...queryKeys.products.all, "list"] as const,
list: (params: unknown) => [...queryKeys.products.lists(), params] as const,
details: () => [...queryKeys.products.all, "detail"] as const,
detail: (id: string) => [...queryKeys.products.details(), id] as const,
bySlug: (slug: string) => [...queryKeys.products.all, "by-slug", slug] as const,
},
// ... other features
};
Sources: lib/query-keys.ts:8-16
3. Custom Hooks for Data Fetching (Queries)
Custom hooks are created for each feature to encapsulate the logic of data fetching using useQuery. These hooks abstract away the queryKey, queryFn, and other configurations, providing a simple interface for components.
Sources: features/products/hooks/use-products-query.ts, features/orders/hooks/use-orders-query.ts
3.1. Example: useProducts
The useProducts hook fetches a paginated list of products. It takes optional params for filtering and pagination, which are included in the query key to ensure unique caching for different queries. It also overrides the default staleTime to 1 minute.
// features/products/hooks/use-products-query.ts:22-29
export function useProducts(
params?: GetProductsParams,
): UseQueryResult<PaginatedResponse<Product>, Error> {
return useQuery({
queryKey: queryKeys.products.list(params),
queryFn: () => productsApi.getAll(params),
staleTime: 60 * 1000, // 1 minute
});
}
Sources: features/products/hooks/use-products-query.ts:22-29
The following sequence diagram shows the data flow when a component uses the useProducts hook.
3.2. Available Query Hooks
| Hook Name | Description | Source File |
|---|---|---|
useProducts | Fetches a paginated list of products. | features/products/hooks/use-products-query.ts |
useProduct | Fetches a single product by its ID. | features/products/hooks/use-products-query.ts |
useProductBySlug | Fetches a single product by its slug. | features/products/hooks/use-products-query.ts |
useOrders | Fetches a paginated list of user's orders. | features/orders/hooks/use-orders-query.ts |
useOrder | Fetches a single order by its ID. | features/orders/hooks/use-orders-query.ts |
useAdminProducts | Fetches products for the admin panel. | features/products/hooks/use-admin-products.ts |
Sources: features/products/hooks/use-products-query.ts, features/orders/hooks/use-orders-query.ts, app/admin/products/page.tsx:28
4. Custom Hooks for Data Mutation
Mutations (create, update, delete operations) are handled using the useMutation hook. These custom hooks encapsulate the mutation logic and, crucially, the cache invalidation logic within the onSuccess callback.
4.1. Example: useUpdateProduct
The useUpdateProduct hook provides a function to update a product. Upon successful mutation, it invalidates both the specific product detail query and all product list queries. This triggers React Query to automatically refetch the updated data, ensuring the UI stays in sync.
// features/products/hooks/use-products-query.ts:88-106
export function useUpdateProduct(): UseMutationResult<
Product,
Error,
{ id: string; data: UpdateProductRequest },
unknown
> {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateProductRequest }) =>
productsApi.update(id, data),
onSuccess: (_, variables) => {
// Invalidate specific product and list
void queryClient.invalidateQueries({
queryKey: queryKeys.products.detail(variables.id),
});
void queryClient.invalidateQueries({
queryKey: queryKeys.products.lists(),
});
},
});
}
Sources: features/products/hooks/use-products-query.ts:88-106
The following sequence diagram illustrates the update and invalidation flow.
4.2. Available Mutation Hooks
| Hook Name | Description | Invalidates | Source File |
|---|---|---|---|
useCreateProduct | Creates a new product. | products.lists() | features/products/hooks/use-products-query.ts |
useUpdateProduct | Updates an existing product. | products.detail(id), products.lists() | features/products/hooks/use-products-query.ts |
useDeleteProduct | Deletes a product. | products.lists() | features/products/hooks/use-products-query.ts |
useCreateOrder | Creates a new order. | orders.lists() | features/orders/hooks/use-orders-query.ts |
useCancelOrder | Cancels an order. | orders.detail(id), orders.lists() | features/orders/hooks/use-orders-query.ts |
useUpdateOrderStatus | Updates an order's status. | orders.detail(id), orders.lists() | features/orders/hooks/use-orders-query.ts |
Sources: features/products/hooks/use-products-query.ts, features/orders/hooks/use-orders-query.ts
Summary
The project's server state management is built on a solid foundation of React Query, promoting a clean separation of concerns. By using a centralized query key factory, feature-specific custom hooks for queries and mutations, and automatic cache invalidation, the architecture ensures a scalable and maintainable system for interacting with backend data. This declarative approach simplifies component logic, improves user experience with optimistic updates and caching, and provides robust error and loading state management out of the box.