Learn how to customise React Grid in 10 minutes

Customers love React datagrid component by ag-Grid for its wide array of customization options. At ag-Grid, we believe that developers should be able to easily extend default functionality to meet their business requirements. That’s why most of the functionality of our React grid is already component based. You can easily extend the default functionality by creating your custom React components and integrating them into the grid.

In the previous article on how to get started with React grid, we’ve integrated ag-Grid into the React application. If you haven’t worked through the tutorial in the article and so don’t have a sample application now, you can download it from this GitHub repository.

We used ag-Grid to render the following data on the screen:

This is image title
A data rendered using React grid

In this tutorial, we’ll extend the default filtering, editing and rendering functionality available for the column “Price”. To do that we’ll implement a custom filter, cell renderer, and cell editor. And we’ll have it all done in less than 10 minutes. Here’s what we’ll add to our React grid.

Custom cell renderer

First, we’re going to implement a custom cell renderer to format numbers to show them according to a user’s locale. So if I’m in Europe the numbers in the column “Price” are displayed like this:

This is image title
Custom cell renderer for the “Price” column

Custom cell editor

Since the “Price” column is numeric, it can’t contain non-numeric characters. By default, when a cell is being edited users can type in any character. So we’ll implement a custom cell editor to restrict the input only to numbers:

This is image title
Custom cell editor for the “Price” column

Custom filter

And we’ll finish this tutorial by implementing a custom filter. Our new filter will provide UI to type in the price range and filter out cars that fall out of that range:

This is image title
Custom filter for the “Price” column

Ready to get started? Let’s see how we can do it.

You can download the sample that we’ll be building in this article from the GitHub repository.

Customization through components

You can customize our React grid component through custom React components. Besides the render method responsible for defining UI these components will also need to implement some methods called by ag-Grid. For example, a cell editor should implement the getValue method that ag-Grid uses to request the value from our component and update the data. A custom column filter should implement the doesFilterPass method that processes values and so on. Interfaces described in the docs define these required methods. As we go through the tutorial, we’ll get familiar with some of them.

Custom cell renderer

The job of the grid is to lay out the cells. By default, the grid will create the cell and render values as simple text. If you want more complex HTML inside the cell or need to customize the value before it’s rendered, you can do it with a custom cell renderer. In this tutorial, we want to format our number according to a user’s locale, so that’s the use case for a custom renderer component. This renderer will take advantage of the built-in function toLocaleString to format values.

Define custom component

To implement the renderer we first need to implement a React component. It will receive the value of a cell through React props. We will render the formatted value inside a simple span HTML element.

So, create a new file NumberFormatter.js and put the code for our component inside:

NumberFormatter.js

import React, { Component } from 'react';

export class NumberFormatter extends Component {
    render() {
        const value = Number(this.props.value);
        const text = value.toLocaleString(undefined, {style: 'currency', currency: 'EUR'});

        return (
            <span>{text}</span>
        )
    }
}

It’s a simple component with just one method render that returns a span element containing the formatted text.

Register the component

Now that we have our React component, we need to tell ag-Grid about it. All custom components should be listed in the frameworkComponents configuration option. So let’s import our custom cell renderer and register it:

App.js

import { NumberFormatter } from './NumberFormatter';

class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            ...
            frameworkComponents: {
                'numberFormatter': NumberFormatter,
            }
        }
    }

    render() {
        return (
            <div
                className="ag-theme-balham"
                style={{height: '200px', width: '600px'}}
            >
                <AgGridReact
                    ...
                    
                    /* this is where we provide custom components */
                    frameworkComponents={this.state.frameworkComponents}
                    
                    rowData={this.state.rowData}>
                </AgGridReact>
            </div>
        );
    }
}

Specify the renderer for the column

We’re almost done. The only thing that’s left is to specify our component as a cell renderer for the “Price” column. We can do that in the column definition:

App.js

class App extends Component {
    constructor(props) {
        this.state = {
            columnDefs: [
                {headerName: 'Make', field: 'make'},
                {headerName: 'Model', field: 'model'},
                {
                    headerName: 'Price',
                    field: 'price',
                    editable: true,

                    /* specify custom cell renderer */
                    cellRenderer: 'numberFormatter'
                }
            ],

Now if you run the application you should see the price formatted:

This is image title

Formatted price according to a user’s locale

Custom cell editor

Our React Grid provides rich editing capabilities. It resembles a spreadsheet allowing you to edit data inline. Just press F2 or double-click on a cell and ag-Grid activates the edit mode. You don’t need to provide a custom editor for simple string editing. But when there’s a need for custom editing logic we need to create our cell editor.

Enabling editing

Before we start working on a custom component that will act as an editor, we need to enable editing for the Price column:

App.js

columnDefs: [
    {headerName: 'Make', field: 'make'},
    {headerName: 'Model', field: 'model'},
    {
        headerName: 'Price',
        field: 'price',
        
        /* enable editing */
        editable: true
    }
]

Define custom component

Similarly to a custom cell renderer, we need to define a React component to act as a cell editor. Let’s start with the basic implementation that will render an input element with a cell value received from ag-Grid. The input will pop up when a user activates the edit mode:

CellEditor.js

export class NumericCellEditor extends Component {
    render() {
        return (
            <input defaultValue={this.props.value}/>
        );
    }
}

Now we need to implement some required methods.

Once editing is finished by pressing Enter or moving the focus away from the input, ag-Grid needs to get the value from our editor. It calls the getValue method on our React component that should return the result of the editing. In our component, it’s the value that the user typed into the input. We need access to that input to read its value. In React we can use the Refs mechanism to accomplish that. Here is the implementation:

CellEditor.js

export class NumericCellEditor extends Component {
    constructor(props) {
        super(props);
        this.textInput = React.createRef();
    }

    render() {
        return (
            <input ref={this.textInput}/>
        );
    }

    getValue() {
        return this.textInput.current.value;
    };
}

Note that I’m using the React.createRef mechanism that is available in the newest versions of React. Check out the documentation to learn how you can do this in previous versions of React.

Now we’re ready to implement the functionality to filter out non-numeric characters. To do that, we need to add an event listener to the input and check each item typed in by the user. Here’s how we do it:

CellEditor.js

export class NumericCellEditor extends Component {
    ...

    render() {
        return (
            <input onKeyPress={this.onKeyPress} .../>
        );
    }

    onKeyPress(event) {
        if (!isNumeric(event.nativeEvent)) {
            event.preventDefault();
        }

        function isNumeric(event) {
            return /\d/.test(event.key);
        }
    }
}

What we also want to do is to prevent losing focus when a user presses left and right arrow navigation keys. We can intercept the keydown event and stop propagation if we detect the key:

CellEditor.js

export class NumericCellEditor extends Component {
    constructor(props) {
        super(props);
        this.textInput = React.createRef();
    }

    onKeyDown(event) {
        if (event.keyCode === 39 || event.keyCode === 37) {
            event.stopPropagation();
        }
    }

    componentDidMount() {
        this.textInput.current.addEventListener('keydown', this.onKeyDown);
    }
    
    ...
}

All right, now we’ve got almost everything set up. Here’s the last thing we need to do. We want to bring focus to the input in our custom cell editor as soon as the user activates the edit mode. Conveniently, ag-Grid gives us the afterGuiAttached method that can be used for that purpose. It gets called once after GUI is attached to DOM and we can use the reference to the input element to focus it:

NumericEditor.js

export class NumericCellEditor extends Component {
    ...
    
    afterGuiAttached() {
        if (this.textInput) this.textInput.current.focus();
    }
}

So here is the full code for you to copy/paste it:

NumericEditor.js

import React, { Component } from 'react';

export class NumericCellEditor extends Component {
    constructor(props) {
        super(props);
        this.textInput = React.createRef();
    }

    onKeyPress(event) {
        if (!isNumeric(event.nativeEvent)) {
            event.preventDefault();
        }

        function isNumeric(event) {
            return /\d/.test(event.key);
        }
    }

    onKeyDown(event) {
        if (event.keyCode === 39 || event.keyCode === 37) {
            event.stopPropagation();
        }
    }

    afterGuiAttached() {
        if (this.textInput) this.textInput.current.focus();
    };

    getValue() {
        return this.textInput.current.value;
    };

    componentDidMount() {
        this.textInput.current.addEventListener('keydown', this.onKeyDown);
    }

    render() {
        return (
            <input onKeyPress={this.onKeyPress} ref={this.textInput} defaultValue={this.props.value}/>
        );
    }
}

Register the component

Similarly to a custom cell renderer, we need to register our cell editor in the frameworkComponents:

App.js

class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            ...
            frameworkComponents: {
                'numericCellEditor': NumericCellEditor
            }

Specify the editor for the column

Once we have our component registered, we can specify it as an editor for the “Price” column:

App.js

columnDefs: [
    {headerName: 'Make', field: 'make'},
    {headerName: 'Model', field: 'model'},
    {
        headerName: 'Price',
        field: 'price',
        editable: true,
        cellRenderer: 'numberFormatter',
        
        /* custom cell editor */
        cellEditor: 'numericCellEditor'
    }
]

And that’s it. We’re all good now to start working on our last task of implementing a custom filter.

Custom column filter

Filtering is one of the most useful features of data grids. It allows users to zoom in on a particular set of records. Our React grid provides a simple string filtering out of the box. But when you need your custom filter types, custom filter components is the way to go. The component we will be working on provides capability filter out cars based on a price range.

Enabling filtering

Before getting to the implementation of the filter component, we need to enable filtering in the grid:

App.js

<AgGridReact
    columnDefs={this.state.columnDefs}
    rowData={this.state.rowData}
    
    /* enable filtering */
    enableFilter={true}
    
    frameworkComponents={this.state.frameworkComponents}>
</AgGridReact>

Define custom component

To implement a custom filter, we follow the familiar customization approach and define a React component. The UI for our filter will be rendered as an input and a button to apply the filter. Let’s put this HTML into the render method of the component:

RangeFilter.js

export class RangeFilter extends Component {
    render() {
        return (
            <form>
                <input name="filter"/>
                <button>Apply</button>
            </form>
        );
    }
}

Users will use our filter UI to specify a range for a car price in the form of lower boundary — upper boundary:

This is image title

Specifying price range using a custom filter

We need to store the current filter condition in the component’s state, so we declare the filter property. When the input is shown, we want to pre-fill it with the current filtering condition. To do that, we can use the defaultValue property. Here’s the code that implements this:

RangeFilter.js

export class RangeFilter extends Component {
    constructor(props) {
        this.state = {
            filter: ''
        };
        ...
    }

    render() {
        return (
            <form>
                <input name="filter" defaultValue={this.state.filter}/>
                <button>Apply</button>
            </form>
        );
    }
}

Then we need to process user input and save it to the component’s state. So we register an event listener on the form and process input when the form is submitted using the Apply button:

RangeFilter.js

export class RangeFilter extends Component {
    constructor(props) {
        super(props);

        this.state = {filter: ''};

        this.onSubmit = this.onSubmit.bind(this);
    }

    onSubmit(event) {
        event.preventDefault();

        let filter = event.target.elements.filter.value;

        if (this.state.filter !== filter) {
            this.setState({filter: filter});
        }
    }

    render() {
        return (
            <form onSubmit={this.onSubmit}>
                <input name="filter" defaultValue={this.state.filter}/>
                <button>Apply</button>
            </form>
        );
    }
}

Whenever there’s a change in the filtering condition, we not only need to update the state but also notify ag-Grid about the change. We can do that by calling the filterChangedCallback that ag-Grid provides for us through the component props. Let’s modify the call to setState a little bit and add the notification logic:
RangeFilter.js

export class RangeFilter extends Component {
    ...

    onChange(event) {
        ...

        if (this.state.filter !== filter) {
            this.setState({filter: filter}, () => {
                
                /* notify ag-Grid about the change */
                this.props.filterChangedCallback();
            });
        }
    }
}
view rawRangeFilter.js hos

We’re done now with the user interface.

Next, we need to implement the doesFilterPass method that performs filtering. It’s called by ag-Grid to determine whether a value passes the current filtering condition or not. We also need to implement the method isFilterActive that is used by ag-Grid to determine whether the component has any filtering condition to apply.

Let’s add this functionality to our React component:

RangeFilter.js

export class RangeFilter extends Component {
    ...

    constructor(props) {
        ...

        this.valueGetter = this.props.valueGetter;
    }

    isFilterActive() {
        return this.state.filter !== '';
    }

    doesFilterPass(params) {
        const filter = this.state.filter.split('-');
        const gt = Number(filter[0]);
        const lt = Number(filter[1]);
        const value = this.valueGetter(params.node);

        return value >= gt && value <= lt;
    }
}

Notice that we also receive the valueGetter function through component props. It’s the function provided by ag-Grid to retrieve the current value of a cell.

Saving and restoring filters

ag-Grid implements API that can be used to activate and deactivate filters on demand. Usually, this API is triggered by some UI element, like a button:

This is image title
UI controls to save and restore filters

For this functionality to work, our custom component should implement two methods — setModel and getModel. ag-Grid calls setModel to activate the filter and getModel to obtain the current filtering condition. Here’s how we implement these methods in code:

RangeFilter.js

export class RangeFilter extends Component {
    ...

    getModel() {
        return {filter: this.state.filter};
    }

    setModel(model) {
        const filter = model ? model.filter : '';
        this.setState({filter: filter});
    }
}

We’re almost ready. The last thing we need to do is bring focus to input when it’s shown. To do that we’ll use the familiar afterGuiAttached callback:

RangeFilter.js

export class RangeFilter extends Component {
    ...
    afterGuiAttached(params) {
        this.input.current.focus();
    }
}

Complete implementation

So here is the full code for our component:
RangeFilter.js

import React, { Component } from 'react';

export class RangeFilter extends Component {
    constructor(props) {
        super(props);

        this.input = React.createRef();

        this.state = {
            filter: ''
        };

        this.valueGetter = this.props.valueGetter;

        this.onSubmit = this.onSubmit.bind(this);
    }

    isFilterActive() {
        return this.state.filter !== '';
    }

    doesFilterPass(params) {
        const filter = this.state.filter.split('-');
        const gt = Number(filter[0]);
        const lt = Number(filter[1]);
        const value = this.valueGetter(params.node);

        return value >= gt && value <= lt;
    }

    getModel() {
        return {filter: this.state.filter};
    }

    setModel(model) {
        const filter = model ? model.filter : '';
        this.setState({filter: filter});
    }

    afterGuiAttached(params) {
        this.input.current.focus();
    }

    onSubmit(event) {
        event.preventDefault();

        let filter = event.target.elements.filter.value;

        if (this.state.filter !== filter) {
            this.setState({filter: filter}, () => {
                this.props.filterChangedCallback();
            });
        }
    }

    render() {
        return (
            <form onSubmit={this.onSubmit}>
                <input name="filter" ref={this.input} defaultValue={this.state.filter}/>
                <button>Apply</button>
            </form>
        );
    }
}

Use component in column definition

Once we have our component ready, we need to register it in the frameworkComponents :
App.js

class App extends Component {
    constructor(props) {
        super(props);

        this.state = {
            ...
            frameworkComponents: {
                'rangeFilter': RangeFilter,
                ...
            }

And then specify our custom filter for the Price column:

App.js

columnDefs: [
    {headerName: 'Make', field: 'make'},
    {headerName: 'Model', field: 'model'},
    {
        headerName: 'Price',
        field: 'price',
        editable: true,
        cellRenderer: 'numberFormatter',
        cellEditor: 'numericCellEditor',

        /* custom column filter */
        filter: 'rangeFilter'
    }
],

Build Your Own React App With ag-Grid!

I hope that the examples above provided a clear illustration of how easy it is to customize and configure our React grid.

Now take the next step and start using ag-Grid in your project!

You’ll see it for yourself how fast and extensible the grid is and what makes it the best React datagrid in the world.

Join the millions of developers and thousands of companies who use ag-Grid to provide the best possible experience to their users.

#reactjs #javascript #programming

Learn how to customise React Grid in 10 minutes
60.55 GEEK