Skip to main content

DataTable - Types

Types and state helpers for DataTable.

Submit feedback
github
import { createColumn, useDataTable } from '@uhg-abyss/web/hooks/useDataTable';
import type {
ColumnFiltersState,
ColumnOrderState,
ColumnPinningState,
DataTableColumn,
DataTableRowData,
GlobalFilterState,
PaginationState,
RowSelectionState,
SortingState,
} from '@uhg-abyss/web/hooks/useDataTable';

State types

Use the exported table state types to strongly type your useState hooks.

const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [columnOrder, setColumnOrder] = useState<ColumnOrderState>([]);
const [columnPinning, setColumnPinning] = useState<ColumnPinningState>({});
const [sorting, setSorting] = useState<SortingState>([]);
const [globalFilter, setGlobalFilter] = useState<GlobalFilterState>('');
const dataTableProps = useDataTable({
initialData: data,
initialColumns: columns,
tableConfig: {
state: {
columnFilters,
pagination,
rowSelection,
columnOrder,
columnPinning,
sorting,
globalFilter,
},
onColumnFiltersChange: setColumnFilters,
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
onColumnOrderChange: setColumnOrder,
onColumnPinningChange: setColumnPinning,
onSortingChange: setSorting,
onGlobalFilterChange: setGlobalFilter,
},
});

Typing columns

Teams looking for additional column type safety can use the DataTableColumn type and the createColumn function to define columns with full TypeScript support.

Using DataTableColumn type

Use DataTableColumn to type your columns array:

type Person = {
uniqueId: string;
firstName: string;
lastName: string;
age: number;
visits: number;
status: 'relationship' | 'complicated' | 'single';
};
const columns: DataTableColumn<Person>[] = [
{
header: 'First Name',
accessorKey: 'firstName',
cell: (info) => {
const row = info.row.original;
const existingData = row.lastName; // ✓ TypeScript knows this exists
const nonExistentData = row.pizza; // ✗ TypeScript error - property doesn't exist
return info.getValue();
},
footer: 'Footer 1',
meta: {
headerLabel: 'First Name Column',
},
},
{
header: 'Age',
accessorKey: 'age',
},
];
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
});

Using createColumn helper

The createColumn function provides type inference for individual column definitions:

type Person = {
uniqueId: string;
firstName: string;
lastName: string;
age: number;
};
const columns = [
createColumn<Person>({
header: 'First Name',
accessorKey: 'firstName', // ✓ TypeScript validates this key exists in Person
cell: (info) => {
const value = info.getValue(); // ✓ Typed as string
return value.toUpperCase();
},
}),
createColumn<Person>({
header: 'Age',
accessorKey: 'age', // ✓ TypeScript validates this key exists
cell: (info) => {
const value = info.getValue(); // ✓ Typed as number
return `${value} years old`;
},
}),
];
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
});

Typed columns and rows

Use DataTableColumn and DataTableRowData to type your columns and data.

type Row = {
uniqueId: string;
col1: string;
col2: string;
};
const columns: DataTableColumn<Row>[] = [
{
header: 'Column 1',
accessorKey: 'col1',
},
{
header: 'Column 2',
accessorKey: 'col2',
},
];
const dataTableProps = useDataTable<Row>({
initialData: data,
initialColumns: columns,
});

Row identification and type safety

Default: uniqueId field

DataTable uses a uniqueId field by default. When your data type includes this field,rowIdKey is optional:

type Person = {
uniqueId: string; // ✓ DataTable will use this automatically
firstName: string;
lastName: string;
};
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
// rowIdKey is optional - uniqueId will be used by default
});

Custom ID field: rowIdKey required

TypeScript enforces that rowIdKey must be provided when your data type lacks a uniqueId field. This compile-time type safety prevents runtime errors from missing row identifiers.

type Person = {
applicationGuid: string; // Custom ID field
firstName: string;
lastName: string;
// No uniqueId field
};
// ❌ TypeScript error: rowIdKey is required
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
});
// ✓ Correct - rowIdKey specified
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
rowIdKey: 'applicationGuid',
});

Why this matters

Row identifiers are critical for DataTable features like row selection, editing, drag-and-drop, and state management. The type system ensures you never forget to specify how rows should be uniquely identified, catching configuration errors at compile-time instead of runtime.

Configuration types

The following examples show how to type all DataTable configuration options.

Action column configuration

import type {
ActionColumnConfig,
ActionDropdownItems,
} from '@uhg-abyss/web/hooks/useDataTable';
type Person = { uniqueId: string; name: string; status: string };
// Type dropdown action items
const actionItems: ActionDropdownItems<Person>[] = [
{
label: 'Edit',
icon: <IconSymbol icon="edit" />,
onClick: ({ row, modifyRow }) => {
modifyRow(row, { status: 'editing' });
},
checkDisabled: (row) => row.original.status === 'locked',
},
{
label: 'Delete',
icon: <IconSymbol icon="delete" />,
onClick: ({ row, deleteRow }) => {
deleteRow(row);
},
isSeparated: true,
},
];
// Type action config
const actionConfig: ActionColumnConfig<Person> = {
actionMode: 'dropdown',
items: actionItems,
dropdownConfig: {
label: 'Actions',
disableWhenAllItemsDisabled: true,
},
};
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
actionColumnConfig: actionConfig,
});

Download dropdown menu items

import type { DownloadDropdownMenuItem } from '@uhg-abyss/web/ui/DataTable';
const downloadMenuItems: DownloadDropdownMenuItem[] = [
{
title: 'Export All',
onClick: 'exportAllData',
icon: <IconSymbol icon="download" />,
},
{
title: 'Export Filtered',
onClick: 'exportFilteredData',
},
{
title: 'Custom Export',
onClick: (tableInstance) => {
const data = tableInstance.getFilteredRowModel().rows;
// Custom export logic with full type safety
},
},
];
<DataTable.DownloadDropdown dropdownMenuItems={downloadMenuItems} />;

Column filter configuration

import type { ColumnFilterConfig } from '@uhg-abyss/web/hooks/useDataTable';
const filterConfig: ColumnFilterConfig = {
defaultSettings: {
filterMode: 'advanced',
caseSensitive: false,
textDefaultCondition: 'contains',
dateDefaultCondition: 'equals',
},
individualSettings: {
firstName: {
inputConfig: { type: 'text' },
defaultCondition: 'startsWith',
caseSensitive: true,
},
birthDate: {
inputConfig: { type: 'date' },
defaultCondition: 'between',
},
status: {
inputConfig: {
type: 'select',
options: [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
],
},
},
},
};
const dataTableProps = useDataTable({
initialData: data,
initialColumns: columns,
columnFilterConfig: filterConfig,
});

Expand column configuration

import type { ExpandColumnConfig } from '@uhg-abyss/web/hooks/useDataTable';
type Person = { uniqueId: string; name: string; details: string };
const expandConfig: ExpandColumnConfig<Person> = {
expandMode: 'subComponent',
renderSubComponent: ({ row }) => (
<div>
<h4>{row.original.name}</h4>
<p>{row.original.details}</p>
</div>
),
subComponentHeight: 200,
};
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
expandColumnConfig: expandConfig,
});

Drag and drop configuration

import type { DragAndDropConfig } from '@uhg-abyss/web/hooks/useDataTable';
type Person = { uniqueId: string; name: string };
const dragDropConfig: DragAndDropConfig<Person> = {
enableRowReorder: true,
enableColumnReorder: true,
onRowsReordered: (oldIndex, newIndex, prevData, updatedData) => {
console.log('Rows reordered', { oldIndex, newIndex });
// Save new order to backend
},
onColumnsReordered: (oldIndex, newIndex, prevOrder, updatedOrder) => {
console.log('Columns reordered', { prevOrder, updatedOrder });
},
};
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
dragAndDropConfig: dragDropConfig,
});

Edit cell configuration

import type { EditCellConfig } from '@uhg-abyss/web/hooks/useDataTable';
type Person = { uniqueId: string; name: string; status: string };
const editConfig: EditCellConfig<Person> = {
enableColumnEdit: true,
canEditRow: (row) => row.status !== 'locked',
onEditCompleted: (previousRow, updatedRow) => {
console.log('Edit completed', { previousRow, updatedRow });
// Save changes to backend
},
};
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
editCellConfig: editConfig,
});

Select column configuration

import type { SelectColumnConfig } from '@uhg-abyss/web/hooks/useDataTable';
type Person = { uniqueId: string; name: string };
const selectConfig: SelectColumnConfig<Person> = {
selectionMode: 'multi',
};
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
selectColumnConfig: selectConfig,
});

Pagination configuration

import type { PaginationConfig } from '@uhg-abyss/web/hooks/useDataTable';
const paginationConfig: PaginationConfig = {
enablePagination: true,
pageSizeOptions: [10, 25, 50, 100],
};
const dataTableProps = useDataTable({
initialData: data,
initialColumns: columns,
paginationConfig,
});

Complete example with all configs typed

import { useState } from 'react';
import { useDataTable } from '@uhg-abyss/web/hooks/useDataTable';
import type {
ActionColumnConfig,
ColumnFilterConfig,
DataTableColumn,
DefaultSettingsConfig,
DragAndDropConfig,
EditCellConfig,
ExpandColumnConfig,
PaginationConfig,
PaginationState,
RowSelectionState,
SelectColumnConfig,
SortingState,
} from '@uhg-abyss/web/hooks/useDataTable';
type Person = {
uniqueId: string;
firstName: string;
lastName: string;
age: number;
status: string;
};
// Type all configurations
const columns: DataTableColumn<Person>[] = [
{ header: 'First Name', accessorKey: 'firstName' },
{ header: 'Last Name', accessorKey: 'lastName' },
{ header: 'Age', accessorKey: 'age' },
];
const paginationConfig: PaginationConfig = {
enablePagination: true,
pageSizeOptions: [10, 25, 50],
};
const columnFilterConfig: ColumnFilterConfig = {
defaultSettings: {
filterMode: 'advanced',
caseSensitive: false,
},
};
const actionConfig: ActionColumnConfig<Person> = {
actionMode: 'dropdown',
items: [
{
label: 'Edit',
icon: <IconSymbol icon="edit" />,
onClick: ({ row }) => console.log('Edit', row.original),
},
],
};
const selectConfig: SelectColumnConfig<Person> = {
selectionMode: 'multi',
};
const defaultSettings: DefaultSettingsConfig = {
rowHeight: 'comfortable',
hideEmptyColumns: false,
};
// Type all state
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 25,
});
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [sorting, setSorting] = useState<SortingState>([]);
// Everything is fully typed
const dataTableProps = useDataTable<Person>({
initialData: data,
initialColumns: columns,
paginationConfig,
columnFilterConfig,
actionColumnConfig: actionConfig,
selectColumnConfig: selectConfig,
defaultSettingsConfig: defaultSettings,
tableConfig: {
state: { pagination, rowSelection, sorting },
onPaginationChange: setPagination,
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
},
});
Table of Contents