Building a React Datagrid

June 15, 2017 0 Comments

Building a React Datagrid

 

 

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 Grid that will display the resulting data. In between the two we'll have a Redux store to act as a bridge between Service and Grid.

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

 git clone https://github.com/ceolter/ag-grid-react-seed.git cd ag-grid-react-seed npm install 

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

First let's start with our GridDataService:

 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: 'ROWDATACHANGED', 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 in our entry file 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/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 'ROWDATACHANGED': 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

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-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

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 // properties columnDefs={this.state.columnDefs} rowData={this.props.rowData} deltaRowDataMode getRowNodeId={(data) => data.symbol} // events onGridReady={this.onGridReady}> </AgGridReact> 

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

If you liked this article then please share


Tag cloud