> hypequery

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>
  );
}

On this page