Skip to main content

DataTable - Editable Data

Displays a matrix of information with columns, rows, and information that can operate dynamically.

Submit feedback
github
import { DataTable } from '@uhg-abyss/web/ui/DataTable';
import { useDataTable } from '@uhg-abyss/web/hooks/useDataTable';

Editable cells

To seamlessly integrate editable cells into your DataTable, use the EditableTableCell sub-component.

import { EditableTableCell } from '@uhg-abyss/web/ui/DataTable';
{
header: 'Column 1',
accessorKey: 'col1',
cell: (props) => {
return <EditableTableCell {...props} inputType="text" />; // Pass in all the props from cell into EditableTableCell
},
footer: 'Footer 1',
}

To display an Abyss-managed column containing buttons for editing data, set editCellConfig.enableColumnEdit to true.

const dataTableProps = useDataTable({
//...
editCellConfig: {
enableColumnEdit: true,
},
//...
});

Here is a basic example with no configuration:

This example shows how to use the value of the cell to determine if the cell should be editable or not.

{
header: 'Column 4',
accessorKey: 'col4',
cell: (props) => {
if (props.getValue() !== 'Completed') {
return <EditableTableCell {...props} />;
}
return props.renderValue();
},
footer: 'Footer 4',
}

By default, when a row is not being edited, the cell value is the return value of the renderValue method. This example combines editable data with custom cell rendering.

{
header: 'Column 4',
accessorKey: 'col4',
cell: (props) => {
const value = props.getValue();
const table = props.table;
const row = props.row;
if (table.options.meta.editActions.rowsInEditMode[row.id]) {
return <EditableTableCell {...props} />;
}
// Custom formatting when not being edited
return (
<React.Fragment>
<IconSymbol icon="home" size="24px" />
{value}
</React.Fragment>
);
},
footer: 'Footer 4',
}

Here is an advanced example of editable data with additional configuration.

  • Columns 1 and 2 are editable.
  • Column 3 uses a custom cell renderer when not in edit mode.
  • Column 4 does not allow the value to be edited if the value is 'Completed'.

Inputs

Input types

To change the input type of an editable cell, use the inputType prop of the EditableTableCell sub-component. The available input types are:

  • text, which renders a TextInput (this is the default)
  • date, which renders a DateInput
  • select, which renders a SelectInput
{
header: 'Column 4',
accessorKey: 'col4',
cell: (props) => {
return <EditableTableCell {...props} inputType="date" />;
},
}

For type 'select', you must also provide the options prop, which is an array of objects with value and label properties.

{
header: 'Column 4',
accessorKey: 'col4',
cell: (props) => {
const options= [
{ value: 'Completed', label: 'Completed' },
{ value: 'Not Completed', label: 'Not Completed' },
{ value: 'In Progress', label: 'In Progress' }
]
return <EditableTableCell {...props} inputType="select" options={options} />;
},
}

Customizing inputs

You can customize the behavior and appearance of each input type using their respective configuration props: textConfig, selectConfig, and dateConfig.

// TextInput
<EditableTableCell {...props} inputType="text" textConfig={{}} />
// DateInput
<EditableTableCell {...props} inputType="date" dateConfig={{}} />
// SelectInput
<EditableTableCell {...props} inputType="select" options={options} selectConfig={{}} />

TextInput

Use textConfig to customize the TextInput behavior and appearance. The available props are:

  • type
  • mask
  • maskConfig
  • maxLength
  • placeholder
  • autoComplete
  • inputLeftElement
  • leftAddOn
  • rightAddOn
  • leftAddOnDescription
  • rightAddOnDescription
  • prefix
  • suffix
  • preserveWhitespace
  • returnUnmaskedValue
  • emptyMaskChar

To learn about prop usage, refer to TextInput.

Note: When using mask / maskConfig with EditableTableCell, most times you will set returnUnmaskedValue to true to maintain consistent data structure for filtering, sorting, and database storage.

DateInput

Use dateConfig to customize the DateInput behavior. The available props are:

  • enableOutsideScroll
  • hidePlaceholder
  • inputLeftElement
  • inputOnly
  • maxDate
  • minDate
  • excludeDate

To learn about prop usage, refer to DateInput.

SelectInput

Use selectConfig to customize the SelectInput behavior. The available props are:

  • isSearchable
  • inputLeftElement
  • enableOutsideScroll
  • virtual
  • maxListHeight

To learn about prop usage, refer to SelectInput.

Row Editing

In the Abyss-managed Edit column, there are three buttons associated with each row:

  • The edit button toggles editing mode for the row
  • The cancel button reverts any changes to the previous unedited data
  • The confirm button updates the table data state to reflect the changes

By default, when editCellConfig.enableColumnEdit is true, all rows will be editable. To disable editing for specific rows, use the editCellConfig.canEditRow function. This function receives the row data as an argument and should return a boolean value indicating whether the row is editable. The example below disables editing for rows where the value of col4 is 'Completed'.

const dataTableProps = useDataTable({
// ...
editCellConfig: {
enableColumnEdit: true,
canEditRow: (row) => {
return row.col4 !== 'Completed';
},
},
// ...
});

Use the editCellConfig.onEditCompleted callback to perform additional actions, such as synchronizing changes with a back-end server, when editing is completed. This callback receives two arguments: previousRow and updatedRow, which represent the row data before and after editing, respectively.

Note: If the data is unchanged, the callback will not be executed.

const dataTableProps = useDataTable({
// ...
editCellConfig: {
enableColumnEdit: true,
onEditCompleted: (previousRow, updatedRow) => {
console.log('Previous row data:', previousRow);
console.log('Updated row data:', updatedRow);
// Additional logic to handle changes
},
},
// ...
});

Validation

Input validation ensures data quality and consistency before saving changes. The EditableTableCell component provides flexible validation by accepting a custom validate function that receives both the cell value and the entire row data, allowing for contextual and cross-field validation.

Note: inputType="select" does not allow usage of validate since an item will always be selected and the options provided will always be valid.

Custom Validation

To add validation to an editable cell, use the validate prop. This function is called on every value change and should return an error message, as a string, if validation fails, or undefined if the value is valid. The function receives two arguments:

  • value: The current cell value being validated
  • row: The entire row data, enabling cross-field validation logic
{
header: 'Status',
accessorKey: 'status',
cell: (props) => {
return (
<EditableTableCell
{...props}
inputType="text"
validate={(value, row) => {
// Basic required validation
if (!value || (typeof value === 'string' && value.trim() === '')) {
return 'This field is required';
}
// Minimum length validation
if (typeof value === 'string' && value.length < 3) {
return 'Must be at least 3 characters';
}
return undefined; // Validation passes
}}
/>
);
},
}

Common Validation Patterns

Pattern validation: Enforce specific formats using regular expressions.

validate={(value, row) => {
if (typeof value === 'string' && value && !/^[A-Z0-9]+$/.test(value)) {
return 'Only uppercase letters and numbers allowed';
}
return undefined;
}}

Cross-field validation: Validate based on the values of other cells in the same row.

validate={(value, row) => {
if (!value) return 'End date is required';
const startDate = new Date(row.startDate);
const endDate = new Date(value as string);
if (endDate <= startDate) {
return 'End date must be after start date';
}
return undefined;
}}

The save button is automatically disabled when any cell has validation errors. Clicking cancel reverts changes and clears all validation errors.

Interactive Example

Custom Action

DataTable provides a way to perform actions on individual rows. This is useful for operations like deleting or programmatically modifying a row's data.

The configuration for editCellConfig.customAction the same pattern / usage as Individual Actions

Note: Please refer to the Individual Row Actions documentation for detailed usage examples and configuration options.

Single Action

Multiple Actions

Programmatically edit cells

To default certain rows to edit mode on initial render, use the initialStateConfig.initialRowsInEditMode property. This property accepts an object where the keys are the unique IDs of the rows and the values are booleans indicating whether that row should be in edit mode.

const dataTableProps = useDataTable({
//...
initialStateConfig: {
initialRowsInEditMode: {
'0': true,
'1': true,
},
//...
},
});

To programmatically update which rows are in edit mode, use setRowsInEditMode method, which accepts the same object format as initialStateConfig.initialRowsInEditMode.

const dataTableProps = useDataTable({
//...
});
const openEditToggle = () => {
dataTableProps.editRowState.setRowsInEditMode({
'2': true,
});
};
//...
return <Button onClick={openEditToggle}>Edit Row 3</Button>;

Component Tokens

Note: Click on the token row to copy the token to your clipboard.

DataTable Tokens

Token NameValue
data-table.color.border.column-header.drag
#002677
data-table.color.border.root
#CBCCCD
data-table.color.border.row.drag
#002677
data-table.color.border.table
#CBCCCD
data-table.color.icon.column-header-menus.grouping.active
#002677
data-table.color.icon.column-header-menus.grouping.hover
#004BA0
data-table.color.icon.column-header-menus.grouping.rest
#196ECF
data-table.color.icon.column-header-menus.sorting.active
#002677
data-table.color.icon.column-header-menus.sorting.hover
#004BA0
data-table.color.icon.column-header-menus.sorting.rest
#196ECF
data-table.color.icon.drag-handle.active
#002677
data-table.color.icon.drag-handle.hover
#004BA0
data-table.color.icon.drag-handle.rest
#196ECF
data-table.color.icon.expander.active
#002677
data-table.color.icon.expander.disabled
#7D7F81
data-table.color.icon.expander.hover
#004BA0
data-table.color.icon.expander.rest
#196ECF
data-table.color.icon.utility.drag-alternative.active
#000000
data-table.color.icon.utility.drag-alternative.disabled
#7D7F81
data-table.color.icon.utility.drag-alternative.hover
#323334
data-table.color.icon.utility.drag-alternative.rest
#4B4D4F
data-table.color.icon.utility.filter.active
#002677
data-table.color.icon.utility.filter.hover
#004BA0
data-table.color.icon.utility.filter.rest
#196ECF
data-table.color.surface.column-header.active
#E5F8FB
data-table.color.surface.column-header.default
#F3F3F3
data-table.color.surface.column-header.drag
#E5F8FB
data-table.color.surface.footer
#F3F3F3
data-table.color.surface.header
#FFFFFF
data-table.color.surface.root
#FFFFFF
data-table.color.surface.row.drag
#E5F8FB
data-table.color.surface.row.even
#FAFCFF
data-table.color.surface.row.highlighted
#E5F8FB
data-table.color.surface.row.hover
#F3F3F3
data-table.color.surface.row.odd
#FFFFFF
data-table.color.surface.table
#FFFFFF
data-table.color.text.cell
#4B4D4F
data-table.color.text.column-header
#4B4D4F
data-table.color.text.header.heading
#002677
data-table.color.text.header.paragraph
#4B4D4F
data-table.border-radius.all.container
8px
data-table.border-width.all.column-header.drag
2px
data-table.border-width.all.root
1px
data-table.border-width.all.row.drag
2px
data-table.border-width.all.table
1px
data-table.sizing.all.icon.column-header-menus
20px
data-table.sizing.all.icon.drag-handle-row
24px
data-table.sizing.all.icon.expander-column
24px
data-table.sizing.all.icon.utility.drag-alternative
20px
data-table.sizing.all.icon.utility.filter
20px
data-table.sizing.height.cell.comfortable
48px
data-table.sizing.height.cell.compact
32px
data-table.sizing.height.cell.cozy
40px
data-table.spacing.gap.horizontal.button-group
8px
data-table.spacing.gap.horizontal.cell
4px
data-table.spacing.gap.horizontal.drag-alternative
8px
data-table.spacing.gap.horizontal.input-container
8px
data-table.spacing.gap.horizontal.slot-wrapper
24px
data-table.spacing.gap.vertical.column-header
2px
data-table.spacing.gap.vertical.header
4px
data-table.spacing.gap.filter-two-inputs
16px
data-table.spacing.padding.all.column-header
8px
data-table.spacing.padding.all.column-header-menus
2px
data-table.spacing.padding.all.header
16px
data-table.spacing.padding.all.result-text
16px
data-table.spacing.padding.all.slot-wrapper
16px
data-table.spacing.padding.horizontal.cell
8px
data-table.spacing.padding.vertical.button-group
8px
data-table.spacing.padding.vertical.cell
4px
data-table.elevation.column.pinned.left
6px 0px 8px -2px rgba(0,0,0,0.16)
data-table.elevation.column.pinned.right
-6px 0px 8px -2px rgba(0,0,0,0.16)
data-table.elevation.column-header
0px 6px 8px -2px rgba(0,0,0,0.16)
data-table.elevation.table-settings-dropdown.section-header
0px 2px 4px -2px rgba(0,0,0,0.16)

Table of Contents