Using Queries
Learn how to use useQuery and useMutation in your React components
Using Queries
Once you've set up @hypequery/react, you can use useQuery and useMutation in your components with full type safety.
useQuery
Fetch data from your hypequery API with automatic caching and refetching:
import { useQuery } from '@/lib/analytics'; function RevenueChart() { const { data, error, isLoading } = useQuery('weeklyRevenue', { startDate: '2025-01-01', }); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return <div>Total: ${data.total}</div>; }
Type Safety
The query name and input are fully typed:
// ✅ Type-safe const { data } = useQuery('weeklyRevenue', { startDate: '2025-01-01', }); // ❌ TypeScript error - invalid query name const { data } = useQuery('invalidQuery', { ... }); // ❌ TypeScript error - invalid input const { data } = useQuery('weeklyRevenue', { invalidField: 'value', });
TanStack Query Options
All TanStack Query options are supported:
const { data } = useQuery('weeklyRevenue', { startDate: '2025-01-01' }, { staleTime: 5 * 60 * 1000, // 5 minutes refetchOnWindowFocus: false, enabled: isAuthenticated, // Conditional fetching } );
useMutation
Execute write operations or actions:
import { useMutation } from '@/lib/analytics'; function RebuildButton() { const rebuild = useMutation('rebuildMetrics'); return ( <button onClick={() => rebuild.mutate({ force: true })} disabled={rebuild.isPending} > {rebuild.isPending ? 'Rebuilding...' : 'Rebuild Metrics'} </button> ); }
Handling Success and Errors
const rebuild = useMutation('rebuildMetrics', { onSuccess: (data) => { console.log('Rebuild complete:', data); // Invalidate related queries queryClient.invalidateQueries({ queryKey: ['weeklyRevenue'] }); }, onError: (error) => { console.error('Rebuild failed:', error); }, });
Optimistic Updates
const updateMetric = useMutation('updateMetric', { onMutate: async (newData) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['metrics'] }); // Snapshot current value const previous = queryClient.getQueryData(['metrics']); // Optimistically update queryClient.setQueryData(['metrics'], (old) => ({ ...old, ...newData, })); return { previous }; }, onError: (err, variables, context) => { // Rollback on error queryClient.setQueryData(['metrics'], context.previous); }, onSettled: () => { // Refetch after success or error queryClient.invalidateQueries({ queryKey: ['metrics'] }); }, });
useMetric and useDataset
When you set up hooks with createAnalyticsHooks, use useMetric and useDataset for semantic endpoints. They take the same TanStack options as useQuery, and the input is the metric or dataset query (dimensions, measures, filters, ordering, limits).
useMetric runs a named metric. Reference it by metric name:
import { useMetric } from '@/lib/analytics'; function RevenueByCountry() { const { data, isLoading } = useMetric('revenue', { dimensions: ['country'], filters: [{ field: 'status', operator: 'eq', value: 'completed' }], orderBy: [{ field: 'revenue', direction: 'desc' }], limit: 10, }); if (isLoading) return <div>Loading...</div>; // Semantic endpoints return `{ data, meta }`. return ( <ul> {data?.data.map((row) => ( <li key={row.country}>{row.country}: {row.revenue}</li> ))} </ul> ); }
useDataset runs an ad hoc dataset query. Reference it by dataset name — the hook maps it to the dataset:<name> route for you:
import { useDataset } from '@/lib/analytics'; function OrdersRollup() { const { data, isLoading } = useDataset('orders', { dimensions: ['country', 'status'], measures: ['revenue', 'orderCount'], filters: [{ field: 'status', operator: 'eq', value: 'completed' }], limit: 25, }); if (isLoading) return <div>Loading...</div>; return ( <table> <tbody> {data?.data.map((row, i) => ( <tr key={i}> <td>{row.country}</td> <td>{row.status}</td> <td>{row.revenue}</td> <td>{row.orderCount}</td> </tr> ))} </tbody> </table> ); }
Both hooks validate against the server-side contract: an invalid dimension, measure, or filter returns a 400 error you can handle the same way as any other query. For cursor-free pagination, see useInfiniteDataset.
Error Handling
Errors from your hypequery API are structured:
const { data, error } = useQuery('weeklyRevenue', { startDate: '2025-01-01' }); if (error) { // Validation errors from hypequery if (error.status === 400) { return <div>Invalid input: {error.message}</div>; } // Network errors if (error.name === 'TypeError') { return <div>Network error. Please check your connection.</div>; } // Generic error return <div>Something went wrong: {error.message}</div>; }
Common Patterns
Dependent Queries
Execute queries in sequence:
function UserRevenue({ userId }: { userId: string }) { const { data: user } = useQuery('getUser', { userId }); const { data: revenue } = useQuery( 'userRevenue', { userId }, { enabled: !!user } // Only run after user is loaded ); return <div>{revenue?.total}</div>; }
Polling
Automatically refetch data at intervals:
const { data } = useQuery( 'liveMetrics', {}, { refetchInterval: 5000 } // Poll every 5 seconds );
Manual Refetch
Trigger refetch on demand:
function MetricsPanel() { const { data, refetch } = useQuery('metrics', {}); return ( <div> <div>{data?.total}</div> <button onClick={() => refetch()}>Refresh</button> </div> ); }