react-datatable
Examples

Date select cell

Use this when a row needs one compact date field such as renewal, due date, or scheduled handoff.

Interactive cell

Live preview

Build the React component

The cell only needs to show the current date clearly and open a small picker. Keep the rest of the date logic outside the render path.

import { CalendarIcon } from "lucide-react"
import { format } from "date-fns"
import { useState } from "react"
import { Calendar } from "@/components/ui/calendar"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"

export function DateSelectCell({
  value,
  onChange,
}: {
  value: string
  onChange: (value: string) => void
}) {
  const [open, setOpen] = useState(false)
  const date = new Date(value + "T00:00:00Z")

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <button className="inline-flex h-7 items-center gap-1.5 rounded-md px-1.5 py-1 text-[12px] leading-4 hover:bg-accent" type="button">
          <CalendarIcon className="size-3 text-muted-foreground" />
          <span>{format(date, "MMM d, yyyy")}</span>
        </button>
      </PopoverTrigger>
      <PopoverContent align="start" className="w-auto p-3">
        <Calendar
          className="p-0"
          mode="single"
          selected={date}
          onSelect={(next) => {
            if (!next) return
            onChange(next.toISOString().slice(0, 10))
            setOpen(false)
          }}
        />
      </PopoverContent>
    </Popover>
  )
}

Wire it into a column definition

The column still keeps the durable field identity. The cell just gives that date field a compact inline picker.

Even with a custom cell, the column still needs an accessor. In this example that is accessorKey="renewal". That gives the table a stable field for sorting, filtering, and saved state, while the cell can still read extra data from row.original.
{
  id: "renewal",
  header: "Renewal",
  accessorKey: "renewal",
  width: 150,
  enableSorting: false,
  enableFiltering: false,
  cell: ({ row }) => (
    <DateSelectCell
      value={row.original.renewal}
      onChange={(renewal) => updateRow(row.original.id, { renewal })}
    />
  ),
}

Render it in a small table

This table renders one custom renewal column beside plain text columns so the integration stays easy to copy.

function RenewalExampleTable() {
  const [rows, setRows] = useState(seedRows)

  const updateRow = (id: string, patch: Partial<CustomerRow>) => {
    setRows((current) => current.map((row) => (row.id === id ? { ...row, ...patch } : row)))
  }

  return <Datatable tableKey="custom-cells" data={rows} columns={columns(updateRow)} getRowId={(row) => row.id} toolbar={false} />
}

Mini table demo

Loading table preferences...

Persist changes with optimistic update

In production, optimistic date updates are usually enough: patch the row locally, send the mutation, then refetch for backend truth.

async function updateRenewal(id: string, renewal: string) {
  setRows((current) => current.map((row) => (row.id === id ? { ...row, renewal } : row)))

  try {
    await api.customers.updateRenewal({ id, renewal })
    await refetchCustomers()
  } catch {
    await refetchCustomers()
  }
}

On this page