How the refine-shadcn Table Component Implements Sorting, Filtering, and Pagination

The refine-shadcn Table component delegates sorting, filtering, and pagination logic to TanStack Table and @refinedev/react-table hooks, rendering Shadcn UI controls that invoke methods like column.toggleSorting(), column.setFilterValue(), and table.setPageIndex().

The refine-shadcn library provides a declarative Table component that wraps TanStack Table and integrates with the Refine data framework. By combining the useTable hook from @refinedev/react-table with dedicated UI field components, it enables server-side and client-side data manipulation through a unified interface.

Sorting Implementation in refine-shadcn

Enabling Sorting in Column Definitions

Sorting is controlled at the column level through the enableSorting property in column definitions. In packages/theme/src/table/index.tsx, the Table component checks both global and column-specific sorting flags before rendering sort controls:

{tableOptions.enableSorting &&
  columnDef.enableSorting && (
    <SortAction column={header.column} />
)}

Source: packages/theme/src/table/index.tsx#L93-L100

When both flags are true, the component injects a SortAction component into the header cell.

The SortAction Component and Toggle Logic

The SortAction component, located in packages/theme/src/table/fields/sort.tsx, renders interactive caret icons that cycle through sort states. Clicking the icon calls column.toggleSorting() with a boolean argument determining the next direction:

onClick={() => {
  column?.toggleSorting(column?.getIsSorted() === "asc");
}}

Source: packages/theme/src/table/fields/sort.tsx#L10-L15

The component uses column.getIsSorted() to determine the current state and applies conditional styling—text-foreground for active sorting and text-input for inactive—providing immediate visual feedback.

Integration with useTable Hook

The useTable hook from @refinedev/react-table maintains sorting state in table.getState().sorting. When toggleSorting() fires, the hook updates this state and triggers a data refetch if the table is bound to a Refine query. The UI automatically re-renders with updated column.getIsSorted() values, synchronizing the header icons with the underlying data order.

Filtering Implementation Strategies

Column Filter Configuration

The Table component determines filter eligibility by checking global and column-specific flags in packages/theme/src/table/index.tsx:

const isFilterable = Boolean(
  tableOptions.enableColumnFilters || tableOptions?.enableFilters,
);

Source: packages/theme/src/table/index.tsx#L58-L63

When a column definition includes a filter property, the component renders that specific filter UI:

{isFilterable &&
  columnDef?.filter &&
  (columnDef.filter({
    column: header.column,
    title: `${columnDef.header} Filter`,
  }) as React.ReactNode)}

Source: packages/theme/src/table/index.tsx#L100-L106

Filter Field Components

The refine-shadcn Table provides three primary filter implementations:

  • TableFilterSearchColumn: Text-based search using column.setFilterValue(e.target.value)
  • TableFilterDropdown: Multi-select dropdown using column.setFilterValue(filterValues)
  • TableFilterDateRangePicker: Date range selection passing array values

The search filter implementation in packages/theme/src/table/fields/table-filter-search-column.tsx binds input changes directly to the column filter state:

<Input
  defaultValue={selectedValue ?? ""}
  onChange={(e) => {
    column?.setFilterValue(e.target.value);
  }}
/>

Source: packages/theme/src/table/fields/table-filter-search-column.tsx#L66-L72

For dropdown filters, the component manages an array of selected values and clears the filter when empty:

column?.setFilterValue(
  filterValues.length ? filterValues : undefined,
);

Source: packages/theme/src/table/fields/table-filter-dropdown.tsx#L22-L28

Filter State Management

useTable stores active filters in table.getState().columnFilters. Each setFilterValue call updates this array, triggering TanStack Table to recompute getFilteredRowModel() and getFilteredSelectedRowModel(). When connected to a Refine data provider, these state changes automatically serialize into query parameters for server-side filtering.

Pagination Architecture

Pagination Component Integration

The Table component renders pagination controls after the table body by passing the table instance to a dedicated Pagination component:

<Pagination table={table} />

Source: packages/theme/src/table/index.tsx#L64-L66

The Pagination component in packages/theme/src/table/fields/pagination.tsx reads current state from table.getState().pagination and exposes controls for:

  • Page size changes: table.setPageSize(size)
  • Direct page navigation: table.setPageIndex(index)
  • Relative navigation: table.nextPage() and table.previousPage()
  • Boundary jumps: First page via setPageIndex(0) and last page via setPageIndex(table.getPageCount() - 1)

The component also displays selection metadata using table.getFilteredSelectedRowModel().rows.length to indicate how many rows are selected across pages.

Source: packages/theme/src/table/fields/pagination.tsx#L27-L104

Hook-Based State Synchronization

The useTable hook maintains pagination state in table.getState().pagination. Method calls like setPageIndex() update this state, causing TanStack Table to recalculate getPageCount() and the visible row model. For server-side implementations, these changes propagate to the Refine query, updating parameters like limit and offset automatically.

Complete Implementation Example

The following example demonstrates a fully configured Table with sorting, filtering, and pagination enabled:

import { Table } from "@/ui/table";

<Table
  showHeader
  columns={[
    {
      id: "title",
      accessorKey: "title",
      header: "Title",
      enableSorting: true,
      filter: Table.Filter.Search,
    },
    {
      id: "status",
      accessorKey: "status",
      header: "Status",
      enableSorting: true,
      filter: Table.Filter.Dropdown,
    },
    {
      id: "created_at",
      accessorKey: "created_at",
      header: "Created At",
      filter: Table.Filter.DateRangePicker,
    },
  ]}
/>

In this configuration:

  1. Sorting is enabled via enableSorting: true, which renders SortAction components in headers
  2. Filtering is provided by assigning Table.Filter.Search, Table.Filter.Dropdown, or Table.Filter.DateRangePicker to the filter property
  3. Pagination is automatically rendered below the table body using the internal Pagination component

The Table component wires these properties into TanStack Table's API through the useTable hook, creating a cohesive data grid experience.

Summary

  • Sorting relies on column.toggleSorting() and column.getIsSorted() managed by the SortAction component in header cells, with state handled by @refinedev/react-table.
  • Filtering uses column.setFilterValue() through specialized filter field components (Search, Dropdown, DateRangePicker) that render conditionally based on column configuration.
  • Pagination is implemented via the Pagination component calling table.setPageIndex(), table.nextPage(), and table.setPageSize() against the TanStack Table instance.
  • All features integrate with Refine's data fetching layer through the useTable hook, automatically synchronizing UI state with server queries.

Frequently Asked Questions

How does refine-shadcn Table handle server-side sorting?

The Table component delegates server-side sorting to the useTable hook from @refinedev/react-table. When column.toggleSorting() is invoked, the hook updates the sorting state and triggers a Refine query refetch, serializing sort parameters into the request. The UI re-renders only after the server returns sorted data, ensuring the displayed rows match the backend state.

What filter types are available in the Table component?

The component provides three built-in filter types via Table.Filter: Search (text input), Dropdown (multi-select), and DateRangePicker (calendar range). Each implements column.setFilterValue() with appropriate value formats—strings for search, arrays for dropdowns, and date tuples for range pickers. Custom filter components can also be passed to the filter column property.

Can pagination be customized or disabled?

Pagination controls render automatically when the table instance includes pagination state, but you can customize the UI by replacing the internal Pagination component or modifying packages/theme/src/table/fields/pagination.tsx. To disable pagination entirely, omit the pagination configuration from the useTable hook options, which prevents the Pagination component from rendering in packages/theme/src/table/index.tsx.

How does the Table component connect to Refine data providers?

The connection happens through the useTable hook, which bridges TanStack Table state with Refine's data fetching layer. When sorting, filtering, or pagination state changes, useTable translates these into Refine query parameters (like sorters, filters, pagination) and calls the configured data provider. The returned data populates table.getRowModel(), rendering the updated rows while maintaining the Shadcn UI styling.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →