Editing
By default, editing is not enabled.
To enable editing globally, you can use the columnDefaultEditable
boolean prop on the InfiniteTable
component. This will enable the editing on all columns.
Or you can be more specific and choose to make individual columns editable via the column.defaultEditable
prop. This overrides the global columnDefaultEditable
.
All columns (except id) are editable.
import { InfiniteTable, DataSource, InfiniteTablePropColumns, } from '@infinite-table/infinite-react'; import * as React from 'react'; type Developer = { id: number; firstName: string; lastName: string; country: string; city: string; currency: string; preferredLanguage: string; stack: string; canDesign: 'yes' | 'no'; hobby: string; salary: number; age: number; }; const dataSource = () => { return fetch('https://infinite-table.com/.netlify/functions/json-server' + '/developers100') .then((r) => r.json()) .then((data: Developer[]) => data); }; const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id', defaultWidth: 80, defaultEditable: false }, stack: { field: 'stack', contentFocusable: true, header: 'Stack', }, firstName: { field: 'firstName', header: 'Name', }, age: { field: 'age', header: 'Age', }, hobby: { field: 'hobby', header: 'Hobby', }, preferredLanguage: { header: 'Language', field: 'preferredLanguage', }, }; export default function ColumnValueGetterExample() { return ( <> <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> columns={columns} columnDefaultEditable /> </DataSource> </>
Column Editors
Read about how you can configure various editors for your columns.
Editing Flow Chart
A picture is worth a thousand words - see a chart for the editing flow.
Note
The column.defaultEditable
property can be either a boolean
or a function
.
If it is a function, it will be called when an edit is triggered on the column. The function will be called with a single object that contains the following properties:
value
- the current value of the cell (the value currently displayed, so aftercolumns.valueFormatter
andcolumns.renderValue
have been applied)rawValue
- the current value of the cell, but before any formatting and custom rendering has been applied. This is either the field value from the current data object, or the result of the columnvalueGetter
function.data
- the data object (of typeDATA_TYPE
) for the current rowrowInfo
- the row info object that underlies the rowcolumn
- the current column on which editing is invokedapi
- a reference to the InfiniteTable APIdataSourceApi
- - a reference to the DataSource API
The function can return a boolean value or a Promise that resolves to a boolean value - this means you can asynchronously decide whether the cell is editable or not.
Making column.defaultEditable
a function gives you the ability to granularly control which cells are editable or not (even within the same column, based on the cell value or other values you have access to).
In addition to the flags mentioned above, you can use the editable
prop on the InfiniteTable
component. This overrides all other properties and when it is defined, is the only source of truth for whether something is editable or not.
Note
The editable
prop allows you to centralize editing logic in one place.
It has the same signature as the column.defaultEditable
function.
Start Editing
Editing can be started either by user interaction or programmatically via the API.
The user can start editing by double-clicking on a cell or by pressing the Enter
key while the cell is active (see Keyboard Navigation for Cells).
To start editing programmatically, use the startEdit({ columnId, rowIndex })
method.
import { InfiniteTable, DataSource, InfiniteTablePropColumns, InfiniteTableApi, } from '@infinite-table/infinite-react'; import { useCallback, useRef, useState } from 'react'; type Developer = { id: number; firstName: string; currency: string; stack: string; hobby: string; salary: string; }; const dataSource: Developer[] = [ { id: 1, firstName: 'John', currency: 'USD', stack: 'frontend', hobby: 'gaming', salary: 'USD 1000', }, { id: 2, firstName: 'Jane', currency: 'EUR', stack: 'backend', hobby: 'reading', salary: 'EUR 2000', }, { id: 3, firstName: 'Jack', currency: 'GBP', stack: 'frontend', hobby: 'gaming', salary: 'GBP 3000', }, { id: 4, firstName: 'Jill', currency: 'USD', stack: 'backend', hobby: 'reading', salary: 'USD 4000', }, ]; const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id', defaultWidth: 80, defaultEditable: false }, salary: { defaultWidth: 320, field: 'salary', header: 'Salary - edit accepts numbers only', style: { color: 'tomato' }, getValueToEdit: ({ value }) => { return parseInt(value.substr(4), 10); }, getValueToPersist: ({ value, data }) => { return `${data!.currency} ${parseInt(value, 10)}`; }, shouldAcceptEdit: ({ value }) => { return parseInt(value, 10) == value; }, }, firstName: { field: 'firstName', header: 'Name', }, currency: { field: 'currency', header: 'Currency', }, }; export default function InlineEditingExample() { const [activeRowIndex, setActiveRowIndex] = useState<number>(2); const apiRef = useRef<InfiniteTableApi<Developer> | null>(null); const onReady = useCallback( ({ api }: { api: InfiniteTableApi<Developer> }) => { apiRef.current = api; }, [], ); return ( <> <button style={{ border: '1px solid magenta', margin: 2, padding: 10, background: 'var(--infinite-background)', color: 'var(--infinite-cell-color)', }} onClick={() => { apiRef.current!.startEdit({ rowIndex: activeRowIndex, columnId: 'salary', }); }} > Edit the salary column for active row </button> <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> onReady={onReady} columns={columns} columnDefaultEditable activeRowIndex={activeRowIndex} onActiveRowIndexChange={setActiveRowIndex} /> </DataSource> </>
Either way, be it user interaction or API call, those actions will trigger checks to see if the cell is editable - taking into account the columnDefaultEditable
, column.defaultEditable
or editable
props, as described in the paragraphs above. Only if the result is true
will the cell editor be displayed.
Customize Edit Value When Editing Starts
When editing starts, the column editor is displayed with the value that was in the cell. This (initial) edit value can be customized via the column.getValueToEdit
prop. This allows you to start editing with a different value than the one that is displayed in the cell - and even with a value fetched asynchronously.
const columns = {
salary: {
field: 'salary',
// this can return a Promise
getValueToEdit: ({ value, data, rowInfo, column }) => {
// suppose the value is a string like '$1000'
// but we want to start editing with the number 1000
return value.replace('$', '');
},
},
};
Try editing the salary column - it has a custom getter for the edit value, which removes the curency string.
import { InfiniteTable, DataSource, InfiniteTablePropColumns, } from '@infinite-table/infinite-react'; type Developer = { id: number; firstName: string; currency: string; stack: string; hobby: string; salary: string; }; const dataSource: Developer[] = [ { id: 1, firstName: 'John', currency: 'USD', stack: 'frontend', hobby: 'gaming', salary: 'USD 1000', }, { id: 2, firstName: 'Jane', currency: 'EUR', stack: 'backend', hobby: 'reading', salary: 'EUR 2000', }, { id: 3, firstName: 'Jack', currency: 'GBP', stack: 'frontend', hobby: 'gaming', salary: 'GBP 3000', }, { id: 4, firstName: 'Jill', currency: 'USD', stack: 'backend', hobby: 'reading', salary: 'USD 4000', }, ]; const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id', defaultWidth: 80, defaultEditable: false }, salary: { defaultWidth: 320, field: 'salary', header: 'Salary - edit accepts numbers only', style: { color: 'tomato' }, getValueToEdit: ({ value }) => { return parseInt(value.substr(4), 10); }, getValueToPersist: ({ value, data }) => { return `${data!.currency} ${parseInt(value, 10)}`; }, shouldAcceptEdit: ({ value }) => { return parseInt(value, 10) == value; }, }, firstName: { field: 'firstName', header: 'Name', }, currency: { field: 'currency', header: 'Currency', }, }; export default function InlineEditingExample() { return ( <> <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> columns={columns} columnDefaultEditable /> </DataSource> </>
Finishing an Edit
An edit is generally finished by user interaction - either the user confirms the edit by pressing the Enter
key or cancels it by pressing the Escape
key.
As soon as the edit is confirmed by the user, the InfiniteTable
needs to decide whether the edit should be accepted or not.
In order to decide (either synchronously or asynchronously) whether an edit should be accepted or not, you can use the global shouldAcceptEdit
prop or the column-level column.shouldAcceptEdit
alternative.
Note
When neither the global shouldAcceptEdit
nor the column-level column.shouldAcceptEdit
are defined, all edits are accepted by default.
Note
Once an edit is accepted, the onEditAccepted
callback prop is called, if defined.
When an edit is rejected, the onEditRejected
callback prop is called instead.
The accept/reject status of an edit is decided by using the shouldAcceptEdit
props described above. However an edit can also be cancelled by the user pressing the Escape
key in the cell editor - to be notified of this, use the onEditCancelled
callback prop.
In this example, the salary
column is configured with a shouldAcceptEdit
function property that rejects non-numeric values.
import { InfiniteTable, DataSource, InfiniteTablePropColumns, } from '@infinite-table/infinite-react'; type Developer = { id: number; firstName: string; currency: string; stack: string; hobby: string; salary: string; }; const dataSource: Developer[] = [ { id: 1, firstName: 'John', currency: 'USD', stack: 'frontend', hobby: 'gaming', salary: 'USD 1000', }, { id: 2, firstName: 'Jane', currency: 'EUR', stack: 'backend', hobby: 'reading', salary: 'EUR 2000', }, { id: 3, firstName: 'Jack', currency: 'GBP', stack: 'frontend', hobby: 'gaming', salary: 'GBP 3000', }, { id: 4, firstName: 'Jill', currency: 'USD', stack: 'backend', hobby: 'reading', salary: 'USD 4000', }, ]; const columns: InfiniteTablePropColumns<Developer> = { id: { field: 'id', defaultWidth: 80, defaultEditable: false }, salary: { defaultWidth: 320, field: 'salary', header: 'Salary - edit accepts numbers only', style: { color: 'tomato' }, getValueToEdit: ({ value }) => { return parseInt(value.substr(4), 10); }, getValueToPersist: ({ value, data }) => { return `${data!.currency} ${parseInt(value, 10)}`; }, shouldAcceptEdit: ({ value }) => { return parseInt(value, 10) == value; }, }, firstName: { field: 'firstName', header: 'Name', }, currency: { field: 'currency', header: 'Currency', }, }; export default function InlineEditingExample() { return ( <> <DataSource<Developer> primaryKey="id" data={dataSource}> <InfiniteTable<Developer> columns={columns} columnDefaultEditable /> </DataSource> </>
Persisting an Edit
By default, accepted edits are persisted to the DataSource
via the DataSourceAPI.updateData
method.
To change how you persist values (which might include persisting to remote locations), use the persistEdit
function prop on the InfiniteTable
component.
Note
The persistEdit
function prop can return a Promise
for async persistence. To signal that the persisting failed, reject the promise or resolve it with an Error
object.
After persisting the edit, if all went well, the onEditPersistSuccess
callback prop is called. If the persisting failed (was rejected), the onEditPersistError
callback prop is called instead.