Advanced Patterns
Learn advanced techniques for configuring and optimizing your React hooks with hypequery.
HTTP Method Configuration
By default, useQuery and useMutation issue GET requests. Override HTTP methods per query when you need POST, PUT, or other verbs:
import { createHooks } from '@hypequery/react';
import { InferApiType } from '@hypequery/serve';
import type { api } from '@/analytics/queries';
type Api = InferApiType<typeof api>;
export const { useQuery, useMutation } = createHooks<Api>({
baseUrl: '/api',
config: {
weeklyRevenue: { method: 'GET' }, // Read-only queries
tripStats: { method: 'GET' },
rebuildMetrics: { method: 'POST' }, // Write operations
updateMetric: { method: 'PUT' },
},
});
When to Use Different Methods
- GET: Read-only queries, safe to cache
- POST: Write operations, mutations, or queries with large inputs
- PUT: Updates to specific resources
- DELETE: Removal operations
Auto-Config from Server
Instead of manually maintaining HTTP method configuration, let the server tell the client which methods to use.
Option A: Shared Server Module (Next.js / Remix)
When your React code can import the server bundle (e.g., Next.js App Router), pass the api directly:
// lib/analytics.ts
import { createHooks } from '@hypequery/react';
import { InferApiType } from '@hypequery/serve';
import { api } from '@/analytics/queries';
type Api = InferApiType<typeof api>;
export const { useQuery, useMutation } = createHooks<Api>({
baseUrl: '/api/hypequery',
api, // ✅ Method metadata extracted automatically
});
The api object contains method metadata from your route definitions. This keeps client and server configuration in sync automatically.
Option B: Config Endpoint (SPAs / Vite)
If your frontend can’t import the server module, expose a configuration endpoint:
1. Create a config endpoint:
// app/api/hypequery-config/route.ts
import { extractClientConfig } from '@hypequery/serve';
import { api } from '@/analytics/queries';
export function GET() {
return Response.json(extractClientConfig(api));
}
2. Load config at runtime:
// lib/analytics.ts
import { createHooks } from '@hypequery/react';
import { InferApiType } from '@hypequery/serve';
import type { api } from '@/analytics/queries';
type Api = InferApiType<typeof api>;
let hooksPromise: Promise<ReturnType<typeof createHooks<Api>>> | null = null;
export function getHypequeryHooks() {
if (!hooksPromise) {
hooksPromise = fetch('/api/hypequery-config')
.then((res) => res.json())
.then((config) =>
createHooks<Api>({ baseUrl: '/api/hypequery', config })
);
}
return hooksPromise;
}
3. Initialize in your app:
// app/providers.tsx
import { getHypequeryHooks } from '@/lib/analytics';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
const queryClient = new QueryClient();
export function AppProviders({ children }: { children: React.ReactNode }) {
const [hooks, setHooks] = useState<any>(null);
useEffect(() => {
getHypequeryHooks().then(setHooks);
}, []);
if (!hooks) return <div>Loading...</div>;
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
Query Client Access
Access the TanStack Query client directly for advanced cache management:
import { useQueryClient } from '@tanstack/react-query';
function MetricsPanel() {
const queryClient = useQueryClient();
const { data } = useQuery('metrics', {});
const handleRefresh = () => {
// Invalidate specific queries
queryClient.invalidateQueries({ queryKey: ['metrics'] });
// Or clear entire cache
queryClient.clear();
};
return (
<div>
<div>{data?.total}</div>
<button onClick={handleRefresh}>Refresh</button>
</div>
);
}
Cache Invalidation
Invalidate After Mutations
Automatically refresh related queries when data changes:
import { useMutation, useQuery } from '@/lib/analytics';
import { useQueryClient } from '@tanstack/react-query';
function MetricsManager() {
const queryClient = useQueryClient();
const { data: metrics } = useQuery('metrics', {});
const rebuild = useMutation('rebuildMetrics', {
onSuccess: () => {
// Invalidate related queries
queryClient.invalidateQueries({ queryKey: ['metrics'] });
queryClient.invalidateQueries({ queryKey: ['weeklyRevenue'] });
},
});
return (
<button onClick={() => rebuild.mutate({ force: true })}>
Rebuild
</button>
);
}
Prefetching
Preload data before it’s needed:
import { useQueryClient } from '@tanstack/react-query';
function Dashboard() {
const queryClient = useQueryClient();
useEffect(() => {
// Prefetch on mount
queryClient.prefetchQuery({
queryKey: ['weeklyRevenue', { startDate: '2025-01-01' }],
queryFn: () => fetch('/api/weeklyRevenue?startDate=2025-01-01')
.then(res => res.json()),
});
}, []);
// ...
}
Custom Query Keys
By default, query keys are [queryName, input]. Override for advanced cache control:
export const { useQuery, useMutation } = createHooks<Api>({
baseUrl: '/api',
getQueryKey: (name, input) => {
// Custom key structure
return ['analytics', name, input];
},
});
This is useful when sharing cache with non-hypequery queries or integrating with existing TanStack Query setups.
Request Interceptors
Modify requests before they’re sent (for auth tokens, headers, etc.):
export const { useQuery, useMutation } = createHooks<Api>({
baseUrl: '/api',
fetch: async (url, options) => {
// Add auth token
const token = getAuthToken();
return fetch(url, {
...options,
headers: {
...options?.headers,
Authorization: `Bearer ${token}`,
},
});
},
});
Suspense Mode
Use React Suspense for declarative loading states:
import { Suspense } from 'react';
function MetricsChart() {
// Throws promise while loading
const { data } = useQuery('weeklyRevenue',
{ startDate: '2025-01-01' },
{ suspense: true }
);
return <div>Total: ${data.total}</div>;
}
function Dashboard() {
return (
<Suspense fallback={<div>Loading metrics...</div>}>
<MetricsChart />
</Suspense>
);
}
Error Boundaries
Handle errors declaratively with error boundaries:
import { ErrorBoundary } from 'react-error-boundary';
function Dashboard() {
return (
<ErrorBoundary
fallback={<div>Failed to load metrics</div>}
onError={(error) => console.error('Metrics error:', error)}
>
<MetricsPanel />
</ErrorBoundary>
);
}
Advanced TanStack Query Options
All TanStack Query options are supported:
const { data, refetch, isFetching } = useQuery(
'weeklyRevenue',
{ startDate: '2025-01-01' },
{
// Caching
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 10 * 60 * 1000, // 10 minutes
// Refetching
refetchOnMount: true,
refetchOnWindowFocus: false,
refetchInterval: 30000, // Poll every 30s
// Conditional fetching
enabled: isAuthenticated,
// Retries
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
// Callbacks
onSuccess: (data) => console.log('Data loaded:', data),
onError: (error) => console.error('Query failed:', error),
}
);
Background Refetching
Keep data fresh with background updates:
function LiveMetrics() {
const { data, dataUpdatedAt } = useQuery(
'liveMetrics',
{},
{
staleTime: 0, // Always consider stale
refetchInterval: 5000, // Refetch every 5 seconds
refetchIntervalInBackground: true, // Continue even when tab is hidden
}
);
return (
<div>
<div>Active Users: {data?.activeUsers}</div>
<div className="text-xs text-gray-500">
Updated: {new Date(dataUpdatedAt).toLocaleTimeString()}
</div>
</div>
);
}
Parallel Queries
Execute multiple queries efficiently:
function Dashboard() {
const revenue = useQuery('weeklyRevenue', { startDate: '2025-01-01' });
const users = useQuery('activeUsers', { limit: 100 });
const metrics = useQuery('systemMetrics', {});
// All three queries run in parallel
if (revenue.isLoading || users.isLoading || metrics.isLoading) {
return <div>Loading...</div>;
}
return (
<div>
<RevenueChart data={revenue.data} />
<UserList data={users.data} />
<MetricsPanel data={metrics.data} />
</div>
);
}
Infinite Queries
For paginated data that loads more as you scroll:
import { useInfiniteQuery } from '@tanstack/react-query';
function InfiniteUserList() {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['users', 'infinite'],
queryFn: async ({ pageParam = 0 }) => {
const res = await fetch(
`/api/users?offset=${pageParam}&limit=20`
);
return res.json();
},
getNextPageParam: (lastPage, pages) => {
if (lastPage.length < 20) return undefined;
return pages.length * 20;
},
});
return (
<div>
{data?.pages.map((page, i) => (
<div key={i}>
{page.map((user: any) => (
<div key={user.id}>{user.name}</div>
))}
</div>
))}
{hasNextPage && (
<button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
}
Next Steps
Continue: Standalone Usage - Using the query builder without serve
Or explore:
- React API Reference - Complete API documentation
- Serve Framework - Define reusable queries