Composable DataTable

A powerful, flexible, and composable data table component built with TanStack React-Table. Features server-side pagination, sorting, multiple view modes, and URL state management.

Composable Architecture

Built with composition in mind. Mix and match components like ListView, CardView, and BottomPaginator to create custom table experiences.

Server-Side Operations

Efficient server-side pagination and sorting with TanStack Start server functions. Handles large datasets seamlessly.

URL State Management

Shareable URLs that preserve table state. Perfect for bookmarking specific views or sharing filtered results.

Demo Examples

React State Demo

Demonstrates the DataTable with React state management. Perfect for applications where you don't need URL persistence.

  • • 2,000 car listings with realistic data
  • • Table and card view modes
  • • Server-side pagination and sorting
  • • Interactive actions (update car versions)
View Demo

URL Parameters Demo

Shows how to integrate table state with URL parameters. Great for shareable links and browser history.

  • • 1,000 user records
  • • URL-synced pagination and sorting
  • • Custom sort controls
  • • Bookmarkable table states
View Demo

Code Examples

Basic Usage

import { DataTable } from './components/dataTable/DataTable'
import { ListView, BottomPaginator } from './components/dataTable'
import { useDataTableState } from './components/dataTable/DataTableState'

function MyTable() {
  const { state, handlers } = useDataTableState({
    defaultPageSize: 10,
    defaultSort: { id: 'name', desc: false }
  })

  const fetcher = async (pagination, sorting) => {
    return await fetchData(pagination, sorting)
  }

  const columns = [
    { accessorKey: 'name', header: 'Name' },
    { accessorKey: 'email', header: 'Email' },
    { accessorKey: 'status', header: 'Status' }
  ]

  return (
    <DataTable
      queryKey={['users']}
      fetcher={fetcher}
      columns={columns}
      pagination={state.pagination}
      onPaginationChange={handlers.onPaginationChange}
      sorting={state.sorting}
      onSortingChange={handlers.onSortingChange}
    >
      <ListView />
      <BottomPaginator />
    </DataTable>
  )
}

URL State Management

import { useDataTableURLState } from './components/dataTable/DataTableState'

function MyTableWithURL() {
  const navigate = useNavigate()
  const search = useSearch()
  
  const { state, handlers } = useDataTableURLState(
    search,
    navigate,
    {
      defaultPageSize: 10,
      defaultSort: { id: 'name', desc: false }
    }
  )

  // URL will automatically sync with table state
  // /my-page?pageIndex=2&pageSize=20&sortId=email&sortDesc=true

  return (
    <DataTable
      queryKey={['users']}
      fetcher={fetcher}
      columns={columns}
      pagination={state.pagination}
      onPaginationChange={handlers.onPaginationChange}
      sorting={state.sorting}
      onSortingChange={handlers.onSortingChange}
    >
      <ListView />
      <BottomPaginator />
    </DataTable>
  )
}

Custom Card View

import { CardView } from './components/dataTable/CardView'

function MyCardTable() {
  return (
    <DataTable
      queryKey={['products']}
      fetcher={fetcher}
      columns={columns}
      pagination={state.pagination}
      onPaginationChange={handlers.onPaginationChange}
      sorting={state.sorting}
      onSortingChange={handlers.onSortingChange}
    >
      <CardView
        className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-6"
        renderCard={(product) => (
          <div className="bg-white dark:bg-gray-700 rounded-lg p-4 shadow">
            <h3 className="font-semibold text-lg">{product.name}</h3>
            <p className="text-gray-600 dark:text-gray-300">{product.description}</p>
            <div className="mt-2 text-sm text-gray-500">
              ${product.price.toLocaleString()}
            </div>
          </div>
        )}
      />
      <BottomPaginator layout="centered" />
    </DataTable>
  )
}

Server Function

import { createServerFn } from '@tanstack/react-start'
import z from 'zod'

const getDataServerFn = createServerFn({
  method: 'GET',
})
  .validator(
    z.object({
      pagination: z.object({ 
        pageIndex: z.number(), 
        pageSize: z.number() 
      }),
      sorting: z.object({
        id: z.string(),
        desc: z.boolean(),
      }),
    })
  )
  .handler(async ({ data: input }) => {
    // Your server-side logic here
    const { pagination, sorting } = input
    
    // Fetch data from database
    const data = await fetchFromDatabase({
      offset: pagination.pageIndex * pagination.pageSize,
      limit: pagination.pageSize,
      sortBy: sorting.id,
      sortOrder: sorting.desc ? 'DESC' : 'ASC'
    })
    
    return {
      rows: data.items,
      rowCount: data.totalCount
    }
  })

// Use in component
const getData = useServerFn(getDataServerFn)
const fetcher = (pagination, sorting) => 
  getData({ data: { pagination, sorting } })

Technical Stack

TS

TypeScript

Type-safe development

RT

TanStack Table

Powerful table logic

RS

TanStack Start

Full-stack framework

TW

Tailwind CSS

Utility-first styling

Built with ❤️ using TanStack Table and modern React patterns