Skip to main content

EditableTable

A controlled, inline-editable table. One row edits at a time; editing, adding, and deleting rows are just operations on the controlled value array surfaced through onChange. Built on the base Table and the VEF form module — there is no query or mutation involved.

VEF-specific component. Not part of Ant Design.

When to Use

  • Editing an array of rows inline — e.g. line items on a form, or any small structured list bound to a single value — without a query/mutation round-trip per row.
  • You want per-column read (renderView) and edit (renderEditor) slots, with type-safe field validators via createEditableColumn.
  • Reach for Crud / ProTable instead when rows come from a paginated remote query with their own per-row API calls.

Basic Usage

import { createEditableColumn, EditableTable } from '@vef-framework-react/components';
import type { EditableColumn } from '@vef-framework-react/components';
import { useState } from 'react';

interface Row {
id: string;
name: string;
age: number;
}

const columns: Array<EditableColumn<Row>> = [
createEditableColumn<Row>('name', {
title: 'Name',
validators: { onChange: ({ value }) => (value ? undefined : 'Name required') },
renderEditor: (field) => <field.Input noWrapper />,
}),
createEditableColumn<Row>('age', {
title: 'Age',
renderView: (value) => `${value} yo`,
renderEditor: (field) => <field.InputNumber noWrapper />,
}),
];

export default function Demo() {
const [rows, setRows] = useState<Row[]>([
{ id: '1', name: 'Edward', age: 32 },
{ id: '2', name: 'Helena', age: 28 },
]);

return (
<EditableTable<Row>
columns={columns}
rowKey="id"
value={rows}
onChange={setRows}
/>
);
}

Adding and Deleting Rows

<EditableTable<Row>
columns={columns}
rowKey="id"
value={rows}
creatable
canDelete
createRecord={() => ({ name: '', age: 18 })}
onChange={setRows}
/>

canEdit and canDelete also accept a per-row predicate: canDelete={(row) => row.age >= 18}.

Extra Row Actions

<EditableTable<Row>
columns={columns}
rowKey="id"
value={rows}
renderRowActions={(row) => <a onClick={() => console.log(row)}>View</a>}
onChange={setRows}
/>

Operation Column Overrides

The appended operation column defaults to a fixed 160px width (fitting the built-in two-button action pair). Widen it when renderRowActions adds extra actions:

<EditableTable<Row>
columns={columns}
rowKey="id"
value={rows}
operationColumn={{ title: 'Actions', width: 220 }}
onChange={setRows}
/>

API

EditableTableProps<TRow>

PropTypeDefaultDescription
valueTRow[]requiredControlled row data
onChange(value: TRow[]) => voidEmitted with the next array on every edit / add / delete
columnsArray<EditableColumn<TRow>>requiredColumn definitions with renderView / renderEditor slots
rowKeyDeepKeys<TRow> | ((row: TRow) => string)"key"Row identity: a field name or a function; provides stable identity for the editing row
canEditboolean | ((row: TRow) => boolean)trueWhether a row can enter edit mode
canDeleteboolean | ((row: TRow) => boolean)falseWhether a row can be deleted
creatablebooleanShow the add-row button below the table
createRecord() => Partial<TRow>Factory for a new row's default values when adding
renderRowActions(row: TRow, index: number) => ReactNodeExtra read-mode actions rendered before the built-in Edit / Delete buttons
operationColumnEditableOperationColumnConfigOperation column overrides (title / width / button labels)
sizeTableProps<TRow>["size"]Table density, forwarded to the underlying table
paginationTableProps<TRow>["pagination"]falseClient-side pagination config, forwarded to the underlying table
localeTableProps<TRow>["locale"]Table locale overrides (e.g. a compact emptyText), forwarded to the underlying table

EditableColumn<TRow>

Built via createEditableColumn<TRow>(dataIndex, options) — a type-safe factory that splits options into display fields, the two render slots, and <form.AppField> options (validators, listeners, …) for the editor.

FieldTypeDescription
dataIndexDeepKeys<TRow>Data key of the column; doubles as the form field name while editing
keystringStable column identity; defaults to dataIndex
editablebooleanWhether this column participates in editing; defaults to true
titleReactNodeColumn header
width / minWidthLengthColumn width constraints
align'left' | 'center' | 'right'Cell alignment
fixed'left' | 'right' | booleanFixed column
ellipsisboolean | { showTitle }Truncate overflow text
classNamestringCell class name
renderView(value: unknown, row: TRow, index: number) => ReactNodeRead-only display slot; falls back to the raw value when omitted
renderEditor(field: typeof fieldComponents, context: EditFieldContext<TRow>) => ReactNodeEditor slot; the column stays read-only when omitted
(validators, listeners, …)Any <form.AppField> option accepted by createField, forwarded to the row's draft form

renderEditor's field argument is the same field-component dictionary used by createField (field.Input, field.Select, …); context carries { row, index, rowKey }.

EditableOperationColumnConfig

FieldTypeDescription
titleReactNodeOperation column header; defaults to "操作"
widthLengthOperation column width. Defaults to a fixed 160 px, which comfortably fits the built-in action pair — without a fixed width the flexing table would hand the operation column any leftover space. Widen it when renderRowActions adds extra actions
textsEditableRowActionsTextsCustom labels for the built-in row action buttons

EditableRowActionsTexts

FieldTypeDescription
editReactNodeLabel for the Edit button
saveReactNodeLabel for the Save button
cancelReactNodeLabel for the Cancel button
deleteReactNodeLabel for the Delete button