Editable inventory grid

A line-item grid you edit directly, like a lightweight spreadsheet: product name and quantity are editable inline, a total column is derived automatically, and a footer bar keeps a running grand total. No form modal, no separate edit view.

Editable inventory grid

Edit product, quantity, or price directly in the cells. Total and grand total update automatically.

Product
Qty
Unit price
Total
Widget A
4
$12.50
$50.00
Widget B
2
$34.00
$68.00
Gadget Pro
1
$89.99
$89.99
Connector X
10
$5.25
$52.50
Cable Pack
3
$18.00
$54.00
5 itemsGrand total: $314.49

This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project.

Editable columns with a shared handler

Set editable per column and point every editable column at the sameonCellEdit handler. Branch on the column's id to validate and coerce the incoming value:

function handleCellEdit(row: LineItem, value: string, column: TableColumn<LineItem>) {
  const field = column.id as keyof LineItem;
  setData(prev => prev.map(r => {
    if (r.id !== row.id) return r;
    if (field === 'quantity') return { ...r, quantity: Math.max(0, parseInt(value) || 0) };
    if (field === 'price')    return { ...r, price: Math.max(0, parseFloat(value) || 0) };
    return { ...r, [field]: value };
  }));
}

const columns: TableColumn<LineItem>[] = [
  { id: 'product',  name: 'Product', selector: r => r.product, editable: true, onCellEdit: handleCellEdit },
  { id: 'quantity', name: 'Qty',     selector: r => r.quantity, editable: true, editor: { type: 'number' }, onCellEdit: handleCellEdit, right: true },
  { id: 'price',    name: 'Unit price', selector: r => r.price, editable: true, editor: { type: 'number' }, onCellEdit: handleCellEdit,
    format: r => `$${r.price.toFixed(2)}`, right: true },
];

A derived column that isn't editable

The total column has no editable flag — it's computed straight from the row on every render, so it never drifts out of sync with quantity or price:

{
  id: 'total',
  name: 'Total',
  selector: r => r.quantity * r.price,
  format: r => `$${(r.quantity * r.price).toFixed(2)}`,
  right: true,
}

Running grand total

Reduce over data on every render and show it in a footer bar below the table — it updates automatically as cells are edited, with no extra event wiring:

const grandTotal = data.reduce((sum, r) => sum + r.quantity * r.price, 0);

<div>
  <DataTable columns={columns} data={data} />
  <div className="total-bar">Grand total: ${grandTotal.toFixed(2)}</div>
</div>

Prefer it inside the table itself? Pass a footer to a column instead — seeFooter for the built-in summary row.