Integrating React and Datatables — not as hard as advertised
A few months ago I was looking for a React component to display a data table in one of our web applications at Undertone. In a previous project that wasn’t based on a high level framework/library such as React, we used the Datatables jQuery plugin and we were very happy with the flexibility that it offers. Now, I was looking for something similar which could easily be integrated as a React component into our new application.
After conducting quite extensive research, I learned two things. First, is that it is ill advised to use Datatables with React, because both try to manage the DOM. Second, there was no library which provided the flexibility we needed for our web application (as of the time of my research). The deadline for the project delivery was nearing, and I had to pick something. I decided to go with the unpopular choice and integrate Datatables into my project. The result worked out much better than I expected, and the entire integration process was actually a pretty smooth ride. In the following sections I describe an outline of a project that has a React — Datatables integration that works.
Initial Setup
We’ll use use Create-React-App (CRA) for the scaffolding of our example project. If you’re not familiar with CRA, it is basically a tool to create React applications with no configuration. The first thing we need to do is to install CRA (if you don’t have it installed already) and initialize a new project using the “create-react-app” command (see CRA documentation for details on how to initialize and run a project)
We finish our setup by installing jQuery and Datatables node modules as dev- dependencies.
npm i --save-dev datatables.net jquery
After the modules installation is complete, we remove the unneeded files automatically generated by CRA from the repository. The final result of the project we are creating here in this article can be found in the React-Datatables GitHub repository.
Basic UI
We implement a very basic UI. The users have an input area where they can type a name and a nickname into the appropriate text boxes. When the ‘Add’ button is clicked, the data typed into the text boxes is added into the data table. The name is a unique ‘key’ for each record - if the user types a name that already exists, a new name-nickname pair is not created and only the nickname of the existing pair is updated in the table.
Setting up Datatables
We already have some initial files with empty content created by CRA. We create a new file which contains our component called Table — this component is responsible for rendering and manipulating the table. First, we import both jQuery and Datatables and link them so that Datatables has access to the jQuerey functions. This can be done with the following two lines:
const $ = require('jquery');
$.DataTable = require('datatables.net');
We define two columns which extract the appropriate values from the name-nickname pair we are going to provide to the table:
const columns = [
{
title: 'Name',
width: 120,
data: 'name'
},
{
title: 'Nickname',
width: 180,
data: 'nickname'
},
];
Finally, the component definition itself:
class Table extends Component {
componentDidMount() {
$(this.refs.main).DataTable({
dom: '<"data-table-wrapper"t>',
data: this.props.names,
columns,
ordering: false
});
} componentWillUnmount(){
$('.data-table-wrapper')
.find('table')
.DataTable()
.destroy(true);
} shouldComponentUpdate() {
return false;
} render() {
return (
<div>
<table ref="main" />
</div>);
}
}
A few things to notice here. In the render function, we render a single HTML table element. This is a requirement of Datatables, as it needs a table element to fill with DOM. React will never ‘know’ that there is more DOM inside the table element. We ensure that no re-renders happen by always returning false from shouldComponentUpdate. The table initialization itself needs to happen only once when our component is mounted, because we want to leave all internal DOM manipulation to Datatables. Finally, we need to destroy the table when the component is about to be unmounted. This is done in the componentWillUnmount method with the appropriate Datatables API call.
Currently, our Table component takes its data via props.names, but we did not implement a way to add or update the names. We do this in the next section.
Updating the table
There are two ways in which the array of the name-nickname pairs can be updated. First, by pushing a new name-nickname pair into it. Second, by replacing an existing name-nickname pair with a new pair which has the same name and a different nickname. This is done in another component external to the Table component. (I will not go into the details of how these updates propagate - please check the project repository on GitHub for more details.) Here, we tackle the two types of updates via two different methods. When a new name-nickname pair is added we reload the entire table:
function reloadTableData(names) {
const table = $('.data-table-wrapper')
.find('table')
.DataTable();
table.clear();
table.rows.add(names);
table.draw();
}
We use a standard jQuery selector to find the table instance using the class we provided in componentDidMount (data-table-wrapper). Then we remove all the previous data and load the new data (for brevity I did not append the new name-nickname pair - this also works if you remove a single pair or multiple pairs).
When a pair is updated, we want to find that pair and change the specific piece of data that was changed (the nickname field, in our example):
function updateTable(names) {
const table = $('.data-table-wrapper')
.find('table')
.DataTable();
let dataChanged = false;
table.rows().every(function () {
const oldNameData = this.data();
const newNameData = names.find((nameData) => {
return nameData.name === oldNameData.name;
});
if (oldNameData.nickname !== newNameData.nickname) {
dataChanged = true;
this.data(newNameData);
}
return true; // RCA esLint configuration wants us to
// return something
});
if (dataChanged) {
table.draw();
}
}
We must not forget to re-render the table using the draw API if any data was changed or the changes will not be visible to the user.
The only thing that remains is to distinguish between the two cases and call the appropriate method when the Table component is rendered by React. We do this by updating the code of shouldComponentUpdate to check for the type of change and execute the appropriate method.
shouldComponentUpdate(nextProps) {
if (nextProps.names.length !== this.props.names.length) {
reloadTableData(nextProps.names);
} else {
updateTable(nextProps.names);
}
return false;
}
Now, every time the number of pairs in the props.names changes, we re-render the entire table or update the proper elements. Note, that in a real application the entire array content may change, while the number of elements may stay the same - a case which cannot happen in our example.
Conclusion
It is definitely possible and even quite straightforward to integrate React with Datatables as long as you keep React out of the Datatables DOM. The use case we implemented in this article is quite simple, yet it provides all the building blocks needed to make the integration work in a real-life project.