Inline

The KendoReact Data Grid enables you to create, update, and delete data records inline.

Basic Concepts

The edit mode of the Grid rows is based on the value of the editField property.

import React from 'react';
import ReactDOM from 'react-dom';
import { Grid, GridColumn as Column, GridToolbar } from '@progress/kendo-react-grid';

import { sampleProducts } from './sample-products.jsx';
import MyCommandCell from './my-command-cell.jsx';

class App extends React.Component {

    CommandCell;

    constructor(props) {
        super(props);

        this.state = {
            data: sampleProducts.slice(0)
        };

        this.enterInsert = this.enterInsert.bind(this);
        this.itemChange = this.itemChange.bind(this);

        const enterEdit = this.enterEdit.bind(this);
        const save = this.save.bind(this);
        const cancel = this.cancel.bind(this);
        const remove = this.remove.bind(this);
        this.CommandCell = MyCommandCell(enterEdit,remove,save,cancel, "inEdit");
    }

    enterInsert() {
        const dataItem = { inEdit: true, Discontinued: false };
        const newproducts = this.state.data.slice();
        newproducts.unshift(dataItem);
        this.update(newproducts, dataItem);
        this.setState({
            data: newproducts
        });
    }

    enterEdit(dataItem) {
        this.update(this.state.data, dataItem).inEdit = true;
        this.setState({
            data: this.state.data.slice()
        });
    }

    save(dataItem) {
        dataItem.inEdit = undefined;
        dataItem.ProductID = this.update(sampleProducts, dataItem).ProductID;
        this.setState({
            data: this.state.data.slice()
        });
    }

    cancel(dataItem) {
        if (dataItem.ProductID) {
            let originalItem = sampleProducts.find(p => p.ProductID === dataItem.ProductID);
            this.update(this.state.data, originalItem);
        } else {
            this.update(this.state.data, dataItem, !dataItem.ProductID);
        }
        this.setState({
            data: this.state.data.slice()
        });
    }

    remove(dataItem) {
        dataItem.inEdit = undefined;
        this.update(this.state.data, dataItem, true);
        this.update(sampleProducts, dataItem, true);
        this.setState({
            data: this.state.data.slice()
        });
    }

    itemChange(event) {
        const value = event.value;
        const name = event.field;
        if (!name) {
            return;
        }
        const updatedData = this.state.data.slice();
        const item = this.update(updatedData, event.dataItem);
        item[name] = value;
        this.setState({
            data: updatedData
        });
    }

    update(data, item, remove) {
        let updated;
        let index = data.findIndex(p => p === item || item.ProductID && p.ProductID === item.ProductID);
        if (index >= 0) {
            updated = Object.assign({}, item);
            data[index] = updated;
        } else {
            let id = 1;
            data.forEach(p => { id = Math.max(p.ProductID + 1, id); });
            updated = Object.assign({}, item, { ProductID: id });
            data.unshift(updated);
            index = 0;
        }

        if (remove) {
            data = data.splice(index, 1);
        }

        return data[index];
    }

    render() {
        return (
            <div>
                <Grid
                    style={{ height: '420px' }}
                    data={this.state.data}
                    onItemChange={this.itemChange}
                    editField="inEdit"
                >
                    <GridToolbar>
                        <button
                            title="Add new"
                            className="k-button k-primary"
                            onClick={this.enterInsert}
                        >Add new
                        </button>

                        {this.state.data.filter(p => p.inEdit).length > 0 && (
                            <button
                                title="Cancel current changes"
                                className="k-button"
                                onClick={(e) => this.setState({ data: sampleProducts.slice() })}
                            >Cancel current changes
                            </button>
                        )}
                    </GridToolbar>
                    <Column field="ProductID" title="Id" width="50px" editable={false} />
                    <Column field="ProductName" title="Product Name" />
                    <Column field="FirstOrderedOn" title="First Ordered" editor="date" format="{0:d}" />
                    <Column field="UnitsInStock" title="Units" width="150px" editor="numeric" />
                    <Column field="Discontinued" title="Discontinued" editor="boolean" />
                    <Column cell={this.CommandCell} width="180px" />
                </Grid>
            </div>
        );
    }
}

ReactDOM.render(
    <App />,
    document.querySelector('my-app')
);

Setup

To enable the inline edit mode of the Grid, use a Higher-Order Component and two data collections.

  1. Set the field which will indicate the editable data items by using the editField property. This field is part of the temporary data items which are used during editing. To configure the edit state of the entire row or the field name that can be edited, you can use Boolean values to define that field.

    <Grid
        editField="inEdit"
  2. Configure the command column by defining the command buttons inside the GridCell component. To render the command cell, use a Higher-Order Component which has to receive all functions that will be executed from the command buttons.

    this.CommandCell = MyCommandCell(enterEdit,remove,save,cancel, "inEdit");
    export default function MyCommandCell(enterEdit, remove, save, cancel, editField) {
        return class extends GridCell {
        render() {
            return !this.props.dataItem[editField]
                ? (
                    <td>
                        <button
                            className="k-primary k-button k-grid-edit-command"
                            onClick={(e) => enterEdit(this.props.dataItem)}>
                            Edit
                        </button>
                        <button
                            className="k-button k-grid-remove-command"
                            onClick={(e) => confirm('Confirm deleting: ' + this.props.dataItem.ProductName) && remove(this.props.dataItem)}>
                            Remove
                        </button>
                    </td>
                )
                : (
                    <td>
                        <button
                            className="k-button k-grid-save-command"
                            onClick={(e) => save(this.props.dataItem)}>
                            {this.props.dataItem.ProductID
                                ? 'Update'
                                : 'Add'}
                        </button>
                        <button
                            className="k-button k-grid-cancel-command"
                            onClick={(e) => cancel(this.props.dataItem)}>{this.props.dataItem.ProductID
                                ? 'Cancel'
                                : 'Discard'}
                        </button>
                    </td>
                );
            }
        }
    };
  3. Define a function for the onItemChange event which will handle any input changes during editing. Inside the event, all relevant data, such as the edited data item, the newly entered value, or the edited field, will be available as itemChange parameters.

    <Grid itemChange={this.itemChange}>
    itemChange(event) {
        const value = event.value;
        const name = event.field;
        if (!name) {
            return;
        }
        const updatedData = this.state.data.slice();
        const item = this.update(updatedData, event.dataItem);
        item[name] = value;
        this.setState({
            data: updatedData
        });
    }
  4. Define the functions which will set the item in edit mode or create a new item in edit mode. You can call these functions from the command buttons in the command cell template.

    enterEdit(dataItem) {
            this.update(this.state.data, dataItem).inEdit = true;
            this.setState({
            data: this.state.data.slice()
        });
    }
    enterInsert() {
        const dataItem = { inEdit: true, Discontinued: false };
        const newproducts = this.state.data.slice();
        newproducts.unshift(dataItem);
        this.update(newproducts, dataItem);
        this.setState({
            data: newproducts
        });
    }
  5. Define the functions which will handle the save, cancel, and remove actions. You can call these functions from the command buttons, the toolbar template, or a button which is outside of the Grid.

    save(dataItem) {
        dataItem.inEdit = undefined;
        dataItem.ProductID = this.update(sampleProducts, dataItem).ProductID;
        this.setState({
            data: this.state.data.slice()
        });
    }
    cancel(dataItem) {
        if (dataItem.ProductID) {
            let originalItem = sampleProducts.find(p => p.ProductID === dataItem.ProductID);
            this.update(this.state.data, originalItem);
        } else {
            this.update(this.state.data, dataItem, !dataItem.ProductID);
        }
        his.setState({
            data: this.state.data.slice()
        });
    }
    remove(dataItem) {
        dataItem.inEdit = undefined;
        this.update(this.state.data, dataItem, true);
        this.update(sampleProducts, dataItem, true);
        this.setState({
            data: this.state.data.slice()
        });
    }
  6. Send the requests to update the data base to the server or use an internal function which will locally keep track of all changes.

    update(data, item, remove) {
        let updated;
        let index = data.findIndex(p => p === item || item.ProductID && p.ProductID === item.ProductID);
        if (index >= 0) {
            updated = Object.assign({}, item);
            data[index] = updated;
        } else {
            let id = 1;
            data.forEach(p => { id = Math.max(p.ProductID + 1, id); });
            updated = Object.assign({}, item, { ProductID: id });
            data.unshift(updated);
            index = 0;
    
        if (remove) {
            data = data.splice(index, 1);
        }
        return data[index];
    }
  7. Per column, set the options that are related to editing:

  • editable—Determines if the column is editable.
  • editor—Specifies the data type of the column and, based on that, sets the appropriate editor.
 /