DataTable

DataTable

The DataTable component is a powerful, production-ready table that displays your data with built-in sorting, filtering, pagination, and selection. It's built on top of @tanstack/react-table and designed to work seamlessly with Better Query.

What you'll use it for: Displaying lists of records (users, posts, products, etc.) in a professional table format with features users expect like sorting columns, searching, and pagination.

Key Features

  • Sorting - Click column headers to sort data
  • Filtering - Built-in search and filter capabilities
  • Pagination - Automatic pagination with page controls
  • Row Selection - Select single or multiple rows
  • Responsive - Works on mobile and desktop
  • Customizable - Full control over columns and rendering
  • Type-Safe - Full TypeScript support

Usage

Basic Example

Here's the simplest way to use DataTable:

import { List, DataTable } from '@/components/admin';

export const PostList = () => (
  <List resource="posts">
    <DataTable>
      <DataTable.Col source="id" />
      <DataTable.Col source="title" />
      <DataTable.Col source="status" />
      <DataTable.Col source="created_at" />
    </DataTable>
  </List>
);

What this creates:

  • A table with 4 columns
  • Automatic data fetching from the "posts" resource
  • Default loading and error states

What is source? It's the field name from your data. If your posts have a title field, use source="title" to display it.

With Better Query

For more control, use Better Query directly:

import { useQuery } from 'better-admin';
import { query } from '@/lib/query';
import { DataTable } from '@/components/admin/data-table';

export function UsersList() {
  // Fetch users from database
  const { list } = useQuery("user", query);
  const { data, isLoading } = list.useQuery();

  if (isLoading) return <div>Loading...</div>;

  // Define columns
  const columns = [
    { accessorKey: "name", header: "Name" },
    { accessorKey: "email", header: "Email" },
    { accessorKey: "role", header: "Role" },
  ];

  return <DataTable columns={columns} data={data || []} />;
}

Why use this approach?

  • More control over data fetching
  • Easier to add custom loading states
  • Can transform data before displaying

Components

DataTable.Col

Defines a column in the table. Each column displays one field from your data.

<DataTable.Col 
  source="title"           // Field name from your data
  label="Post Title"       // Column header text
  sortable                 // Enable sorting on this column
  render={(value, record) => (
    <Link to={`/posts/${record.id}`}>{value}</Link>
  )}
/>

Props

PropTypeRequiredDescription
sourcestringYesField name from the data object
labelstringNoColumn header text (defaults to source)
sortablebooleanNoEnable sorting for this column
render(value, record) => ReactNodeNoCustom renderer for cell content
widthstring | numberNoColumn width (e.g., "200px" or 200)

Custom rendering: The render function receives two arguments:

  • value - The value of this field
  • record - The complete data object for this row

This lets you access other fields when rendering. For example, you might show a user's name but link to their full profile.

Examples

Basic Table

import { List, DataTable } from '@/components/admin';

export const PostList = () => (
  <List resource="posts">
    <DataTable>
      <DataTable.Col source="id" label="ID" />
      <DataTable.Col source="title" label="Title" />
      <DataTable.Col source="status" label="Status" />
      <DataTable.Col source="created_at" label="Created" />
    </DataTable>
  </List>
);

With Custom Renderers

import { List, DataTable, EditButton } from '@/components/admin';
import { Badge } from '@/components/ui/badge';

export const PostList = () => (
  <List resource="posts">
    <DataTable>
      <DataTable.Col source="title" />
      <DataTable.Col 
        source="status"
        render={(status) => (
          <Badge variant={status === 'published' ? 'default' : 'secondary'}>
            {status}
          </Badge>
        )}
      />
      <DataTable.Col 
        source="author"
        render={(author) => (
          <div className="flex items-center gap-2">
            <Avatar src={author.avatar} />
            <span>{author.name}</span>
          </div>
        )}
      />
      <DataTable.Col 
        label="Actions"
        render={(_, record) => (
          <div className="flex gap-2">
            <EditButton record={record} />
            <Button variant="outline" size="sm">View</Button>
          </div>
        )}
      />
    </DataTable>
  </List>
);

With Sorting

import { List, DataTable } from '@/components/admin';

export const PostList = () => (
  <List resource="posts">
    <DataTable>
      <DataTable.Col source="title" sortable />
      <DataTable.Col source="created_at" sortable />
      <DataTable.Col source="view_count" sortable />
    </DataTable>
  </List>
);

With Selection

import { List, DataTable } from '@/components/admin';

export const PostList = () => (
  <List resource="posts">
    <DataTable selectable>
      <DataTable.Col source="title" />
      <DataTable.Col source="status" />
    </DataTable>
  </List>
);

Features

Sorting

Columns can be made sortable by adding the sortable prop:

<DataTable.Col source="title" sortable />

Pagination

Pagination is automatically handled based on the data from the List component.

Selection

Enable row selection with the selectable prop:

<DataTable selectable>
  {/* columns */}
</DataTable>

Loading States

The DataTable automatically shows loading states when data is being fetched.

Empty States

Custom empty state when no data is available:

<DataTable emptyState={<div>No posts found</div>}>
  {/* columns */}
</DataTable>

Props

PropTypeDescription
childrenReactNodeDataTable.Col components
selectablebooleanEnable row selection
onSelectionChange(selection) => voidSelection change handler
emptyStateReactNodeCustom empty state

Installation

npx better-admin add data-table

On this page