ag-Grid is The Best Grid in the world! React is one of the best frameworks in the world! Redux and React were made for each other. This blog goes through how to use all three of these frameworks together for a brilliant developer experience!

A live working example of ag-Grid with React and Redux can be found here.

Introduction

React is a great framework offering a powerful but simple way to write your applications. Redux offers a great way to decouple your Component state while making it easier to keep your data immutable.

ag-Grid is not written in React — this makes ag-Grid both powerful and flexible in that we can support all major frameworks. ag-Grid can also work with immutable stores, so although ag-Grid doesn’t use Redux internally, it is fully able to work with your React / Redux application seamlessly. Let me show you how…

Our Application

The completed code for this blog can be found here.

In order to focus on the concepts being discussed our application is deliberately simple.

We will have a simple Service that will simulate a backend service providing updates to the grid, and a simple Gridthat will display the resulting data. In between the two we'll have a Redux store to act as a bridge between Serviceand Grid.

We’ll start off with our ag-Grid React Seed project to get us up and running with a simple skeleton application:

If we now run npm start we'll be presented with a simple Grid:

With this in place, let’s install the Redux dependencies:

npm i redux react-redux

Our application is going to a simple Stock Ticker application — we’ll display 3 Stocks and their corresponding live price.

The Service

The Service

First let’s start with our GridDataService:

// src/GridDataService.js
export default class GridDataService {
    constructor(dispatch) {
        this.dispatch = dispatch;
        this.rowData = [
            {symbol: "AAPL", name: "Apple Corp", price: 154.99},
            {symbol: "GOOG", name: "Google", price: 983.41},
            {symbol: "MSFT", name: "Microsoft", price: 71.95}
        ];
    }
    start() {
        setInterval(() => {
            this.applyPriceUpdateToRandomRow();
            this.dispatch({
                type: 'ROW_DATA_CHANGED',
                rowData: this.rowData.slice(0)
            })
        }, 1500);
    }
    applyPriceUpdateToRandomRow() {
        let swingPositive = Math.random() >= 0.5; // if the price is going up or down
        let rowIndexToUpdate = Math.floor(Math.random() * 3);
        let rowToUpdate = this.rowData[rowIndexToUpdate];
        let currentPrice = rowToUpdate.price;
        let swing = currentPrice / 10; // max of 10% swing
        let newPrice = currentPrice + (swingPositive ? 1 : -1) * swing;
        rowToUpdate.price = newPrice;
    }
}

The service manages the data — it has the current data and makes it available via Redux. In this example we have 3 rows of data (one each for Apple, Google and Microsoft) which the service periodically updates to simulate live price changes.

The service is provided with the Redux stores dispatch (see later) which is uses to publish the data changes.

Bootstrapping The Application

We’ll create the Redux Store and the GridDataService class in our entry file src/index.js:

// src/index.js
'use strict';
import React from "react";
import {render} from "react-dom";
import {Provider} from "react-redux";
import {createStore} from "redux";
// pull in the ag-grid styles we're interested in
import "ag-grid-root/dist/styles/ag-grid.css";
import "ag-grid-root/dist/styles/ag-theme-fresh.css";
// only necessary if you're using ag-Grid-Enterprise features
// import "ag-grid-enterprise";
// our application
import SimpleGridExample from "./SimpleGridExample";
import GridDataService from "./GridDataService";
// a simple reducer
let gridDataReducer = (state = {rowData: []}, action) => {
    switch (action.type) {
        case 'ROW_DATA_CHANGED':
        return {
            ...state,
            rowData: action.rowData,
        };
    default:
        return state;
    }
};
// create the Redux store
let store = createStore(gridDataReducer);
// instantiate our Service and pass in the Redux store dispatch method
let gridDataService = new GridDataService(store.dispatch);
// wait for the dom to be ready, then render our application
document.addEventListener('DOMContentLoaded', () => {
    render(
        // make our application redux aware
        <Provider store={store}>
        <SimpleGridExample/>
        </Provider>,
        document.querySelector('#app')
    );
    // kick off our service updates
    gridDataService.start();
});

Here we do a number of bootstrap tasks:

  • Import our GridDataService
  • Create a simple Redux reducer gridDataReducer
  • Create the Redux Store
  • Instantiate our GridDataService and make the Redux stores dispatch method available to it
  • Render our application
  • Start our service updates

The Application

The Application

We’ll modify the seed SimpleGridExample in the following ways:

  • Delete createRowData - the application will now receive it's data from the Redux store
  • Modify createColumnDefs to reference the new data structure (two columns: "name" and "price")
  • In our Grid properties, refer to this.props.rowData - this will be populated by Redux

The GridDataService now looks like this:

import React, {Component} from "react";
import {AgGridReact} from "ag-grid-react";
import {connect} from "react-redux";
class SimpleGridExample extends Component {
    constructor(props) {
        super(props);
        this.state = {
            columnDefs: this.createColumnDefs()
        }
    }
    onGridReady(params) {
        this.gridApi = params.api;
        this.columnApi = params.columnApi;
        this.gridApi.sizeColumnsToFit();
    }
    createColumnDefs() {
        return [
            {headerName: "Company", field: "name"},
            {headerName: "Price", field: "price", cellFormatter: (params) => params.value.toFixed(2)}
        ];
    }
    render() {
        let containerStyle = {
            height: 115,
            width: 500
        };
        return (
            <div style={containerStyle} className="ag-theme-fresh">
                <h1>Simple ag-Grid React Example</h1>
                <AgGridReact
                    // properties
                    columnDefs={this.state.columnDefs}
                    rowData={this.props.rowData}
                    // events
                    onGridReady={this.onGridReady}>
                </AgGridReact>
            </div>
        )
    }
}
// pull off row data changes
export default connect(
    (state) => {
        return {
            rowData: state.rowData
        }
    }
)(SimpleGridExample);

With a fairly small number of changes to the seed project we now have a React application using both Redux and ag-Grid — fantastic!

With this running you’ll see the prices updating — this looks great. There is one catch here though; the entire grid row data will be re-rendered if we leave the application like this, even though only a single row will be updated at a time.

The Secret Sauce

Secret Sauce

By making use of the new deltaRowDataMode in ag-Grid, we can ensure that we only re-render the data that has actually changed.

To do this all we need to do is specify that we want to make use of deltaRowDataMode, and provide a unique key to ag-Grid so that it can determine what has changed, if anything. We do this by providing the getRowNodeId callback. In our case, each row can be uniquely identified by it's symbol:

<AgGridReact>
    columnDefs={this.state.columnDefs}
    rowData={this.props.rowData}
    deltaRowDataMode
    getRowNodeId={(data) => data.symbol}
    onGridReady={this.onGridReady}>
</AgGridReact>

Visually, the application appears no different with these changes, but performance has dramatically been improved. This would be evident in a larger application, especially one with a large amount of row data.


Learn more about ag-Grid and React