Building a React Data Grid with Redux and ag-Grid
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 to AG Grid and React
- A Simple React Application
- Simple AG Grid React Example
- Only Re-render Data That Has Changed
Introduction to AG Grid and React
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…
A Simple React 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:
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 Backend 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
Add CRUD Functionality to the React 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!
Simple AG Grid React Example
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.
Only Re-render Data That Has Changed
The secret sauce for our application is to make use of the new deltaRowDataMode
in ag-Grid, and 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.
AG Grid has full support for React with a Getting Started Guide and all examples in the documentation have ReactJS versions.