Ant Design Forge

useTable

Connect filter forms to data tables with type-safe queries and automatic state management.

Overview

useTable combines the power of useForm with TanStack Query to create a seamless connection between filter forms and data tables. It manages filter state, query execution, and table props automatically.

Key Features

  • Built on useForm - All type-safe form features from useForm are included
  • Automatic query management - Filters are synced with TanStack Query
  • Type-safe data fetching - Full type inference for filter values and query results
  • Built-in pagination - Server-side pagination with automatic state management
  • Colocated query definitions - Define query options separately with createTableQueryOptions

Usage

See the Basic example for a live demo.

import React from "react";

import { , , ,  } from "antd";
import {  } from "@tanstack/react-query";
import {  } from "zod";
import {  } from "@dyrhoi/antd-forge";

type  = {
  : number;
  : string;
};
declare function (: { ?: string }): <{ : []; : number }>;

const  = .({
  : .().(),
});

function () {
  const { , ,  } = ({
    : ,
    : ({ filters,  }) =>
filters: {
    search?: string | undefined;
}
({ : ["users", ], : () => (), : () => ({ : ., : . }), }), }); // `tableProps.dataSource` is fully typed as `User[]` const dataSourceType = .;
const dataSourceType: readonly User[] | undefined
return ( <> < {...} ="inline"> < ={["search"]} ="Search"> < /> </> < ="primary" ="submit"> Search </> </> < {...} ={[ { : "ID", : "id", : "id", }, { : "Name", : "name", : "name", }, ]} ="id" /> </> ); }

Without TanStack Query

If you're not using TanStack Query, you can use the simpler search function instead. See the Basic search example for a live demo.

const { formProps, FormItem, tableProps } = useTable({
  validator: schema,
  search: async ({ filters, pagination }) => {
    const data = await fetchUsers(filters, pagination.current, pagination.pageSize);
    return { data: data.items, total: data.count };
  },
});

Pagination

useTable provides built-in server-side pagination. The search or queryOptions function receives pagination state, and tableProps automatically wires up the table's pagination UI.

How It Works

  1. Your search function receives { filters, pagination: { current, pageSize } }
  2. Return { data, total } where total is the total count (not page count)
  3. tableProps.pagination is automatically configured with the current state
  4. Page resets to 1 when filters change or page size changes

Custom Initial Values

const { tableProps, pagination } = useTable({
  validator: schema,
  search: searchFn,
  pagination: {
    initial: {
      pageSize: 25,  // Default is 10
      current: 1,
    },
  },
});

// Read current pagination state
console.log(pagination.current, pagination.pageSize, pagination.total);

Using Pagination in Queries

const { tableProps } = useTable({
  validator: schema,
  queryOptions: ({ filters, pagination }) =>
    queryOptions({
      queryKey: ["users", filters, pagination],
      queryFn: () => fetchUsers({
        ...filters,
        page: pagination.current,
        limit: pagination.pageSize,
      }),
      select: (data) => ({ data: data.items, total: data.totalCount }),
    }),
});

Client-Side Pagination

By default, useTable uses server-side pagination where you handle pagination in your API. For smaller datasets, you can use client-side pagination where Ant Design's Table handles pagination from the full dataset. See the Client Pagination example for a live demo.

With client mode:

  • Your search/query function receives no pagination props - just filters
  • Return a plain array instead of { data, total }
  • Ant Design's Table automatically paginates from the full dataSource
  • The pageSize setting controls how many rows display per page
// Client pagination - fetch all data, let Table handle pagination
const { tableProps } = useTable({
  validator: schema,
  pagination: { mode: "client", initial: { pageSize: 10 } },
  search: async ({ filters }) => {
    // Note: no `pagination` in props for client mode
    const allItems = await fetchAllUsers(filters);
    return allItems; // Return array directly, not { data, total }
  },
});

Client Pagination with TanStack Query

When using queryOptions with client pagination, use createTableQueryOptions with the client config:

import { createTableQueryOptions, useTable } from "@dyrhoi/antd-forge";
import { queryOptions } from "@tanstack/react-query";

// Define query options with client pagination mode
const usersQueryOptions = createTableQueryOptions<Filters, User>({
  pagination: { mode: "client" },
})(({ filters }) =>
  queryOptions({
    queryKey: ["users", filters],
    queryFn: () => fetchAllUsers(filters), // Returns User[]
    // No need to transform to { data, total }
  }),
);

function UsersTable() {
  const { tableProps } = useTable({
    validator: schema,
    queryOptions: usersQueryOptions,
    pagination: { mode: "client", initial: { pageSize: 10 } },
  });
  // ...
}

When to Use Client vs Server Pagination

AspectServer Mode (default)Client Mode
Best forLarge datasetsSmall datasets (under 1000 rows)
Data fetchingFetch per pageFetch all at once
Return type{ data, total }TData[]
Pagination logicYour APIAnt Design Table
Network usageLower per requestHigher initial load

Auto Submit

Since useTable is built on useForm, it inherits the autoSubmit option. This is particularly useful for filter forms where you want the table to update as the user types—no submit button required. See the Auto Submit example for a live demo.

For full documentation on modes and configuration, see useForm - Auto Submit.

const { formProps, FormItem, tableProps } = useTable({
  validator: schema,
  autoSubmit: { mode: "auto", debounce: 400 },
  search: ({ filters, pagination }) => fetchData(filters, pagination),
});

Examples

Basic

Using queryOptions with TanStack Query for data fetching.

Using the simpler search function without TanStack Query.

Client Pagination

Fetching all data and letting Ant Design Table handle pagination.

Auto Submit

Automatically updating the table as the user types in the filter form.

On this page