DataTables is a powerful javascript library that is used to generate and manipulate HTML table data in web applications. It has amazing features like paging, sorting, ordering, searching, and filtering abilities. The library has been extensively used in web applications.
DataTables has commonly been used with jQuery in the past few years. However, it can also be used in front-end javascript libraries like React. Although jQuery can be used in React applications, its use in React applications is highly discouraged, unless it becomes inevitable.
In this post, we will examine how we can use DataTables library to generate table data in a React application. We will soon observe that DataTables library can be used in React applications without directly using jQuery. Afterall, the React community frowns on the use of jQuery in React applications. We will also see how we can dynamically manipulate table data with a powerful API that the DataTables library exposes.
Setting Up
In our path to demonstrate the use of DataTables library in a React application, we will first create a starter react application. We will build on this application by installing DataTables package with npm
. Although DataTables has its own CSS styling, it has support for other CSS styling frameworks like Bootstrap and Foundation. In this starter application, we will use the Bootstrap styling for the table that we generate.
To facilitate a quick setup and get going, we will use create-react-app to quickly generate a starter react application. I assume you already have create-react-app package installed. If you prefer to roll your own without using create-react-app, you can read through the extensive discussion on how to create a react application from scratch without using create-react-app tool.
Install DataTables Package
Although you can use any text editor to get along with the discussions in this post, I will be using Visual Studio Code for source code views in discussions that follow.
If you are using VS Code, click on the Terminal menu, then on New Terminal dropdown menu item. If you aren’t using VS code, you can open a terminal window from your working environment.
Before we issue the initial command, recall that we will be using Bootstrap 5 CSS styling for the table we intend to generate. You should run the following command to install the DataTables package with Bootstrap 5 CSS styling:
npm install datatables.net-bs5 bootstrap@5.3.3
If you do not want to use Boostrap 5 styling but rather use the default CSS styling for the DataTables library, then you should run the following command:
npm install datatables.net
As indicated earlier, we will not need jQuery with DataTables package, hence we did not explicitly install jQuery. However, you may see jQuery in your node_modules directory after running the above command. Even if it exists, we will not use it in our implementation code.
Create DataTable Component
In react applications development, it is a good practice to create a components directory to house our own components. However, because this is very small application, we will break that customary rule and create a DataTable
react component in the same directory that contains our App.js
component.
In the src directory, or your directory that contains your App.js component, create another file for the DataTable
component. You can give it the name DataTable.js, or any name of your choosing such as ReactDataTable.js. In my case, I will name the file DataTable.js. After creating the file, the application directory structure should look similar to the following:
Click on DataTable.js file, and in the file editor section, enter or paste the following code:
import React, { useEffect, useRef } from "react";
import DataTables from "datatables.net-bs5";
// A component to generate table
const DataTable = (props) => {
// This will reference the table element
const tableRef = useRef(null);
// return UI for this component
return (
<div className="table-responsive" style={{padding: '12px'}}>
<table
className="table table-bordered table-hover no-wrap"
ref={tableRef}
style={{width: '100%'}}
>
</table>
</div>
)
}
export default DataTable;
From line 1, we have imported useRef
and useEffect
hooks from react.
useRef
to Reference <table></table>
Tag
Since the HTML table
will be initialised after React has mounted our DataTable
component, we need a reference to the <table></table>
tag on line 12. Later, we will use it to initialise with DataTables library. As can be seen, we have a ref
prop to the <table></table>
tag and have set ref
to the tableRef
variable that we have initialised with useRef
on line 7. When the component has mounted, we will use tableRef
, which references the <table></table>
tag, to initialise the table.
useEffect
for Table Initialisation
In addition to the useRef
hook, we have also imported the React useEffect
hook on line 1. useEffect
hook is used to call a function to be executed after a React component has been mounted.
DataTables library requires the table element to be in the browser DOM for initialisation. Hence, after React has mounted our DataTable
component, the <table></table>
tag will be available in the browser DOM, and we will need to call a function to initialise the table with DataTables library. React useEffect
hook does exactly that. When used in a component, useEffect
will run the function that we pass to it as argument after the component has been rendered.
Table Initialisation
To generate our table with DataTables library, we need to call a function to perform the initialisation. The following code extends the previous code by using useEffect
hook to call a function to initialise the table:
import React, { useEffect, useRef } from "react";
import DataTables from "datatables.net-bs5";
// A component to generate table
const DataTable = (props) => {
// a reference to the table element
const tableRef = useRef(null);
// After component is mounted, initialise the table
useEffect(initTable);
function initTable() {
// initialise table with DataTables library and return API
const tableApi = new DataTables(tableRef, {...props});
}
// return UI for this component
return (
<div className="table-responsive" style={{padding: '12px'}}>
<table
className="table table-bordered table-hover no-wrap"
ref={tableRef}
style={{width: '100%'}}
>
</table>
</div>
)
}
export default DataTable;
Listing 2 defines initTable()
function which spans from line 12 to line 15. initTable()
initialises the table with DataTables library by passing tableRef
, which references the <table></table>
tag as first argument, and the table options passed as props
to the component as second argument. On line 10, we call useEffect
hook by passing initTable
as the function to be run when the component is mounted.
Since we will not make a call to initTable()
in any other part of the component, we can pass an anonymous function to useEffect
hook to perform the table initialisation. The following code is a refinement to listing 2:
import Reat, { useEffect, useRef } from "react";
import DataTables from "datatables.net-bs5";
// A component to generate table
const DataTable = (props) => {
// a reference to the table element
const tableRef = useRef(null);
// After component is mounted, initialise the table
useEffect(() => {
// initialise table with DataTables library and return API
const tableApi = new DataTables(tableRef, {...props});
});
// return UI for this component
return (
<div className="table-responsive" style={{padding: '12px'}}>
<table
className="table table-bordered table-hover no-wrap"
ref={tableRef}
style={{width: '100%'}}
>
</table>
</div>
)
}
export default DataTable;
From listing 3, the function that is called to initialise the table is passed as an anonymous function to useEffect
hook. This is a common practice with useEffect
hook usage.
If you are in haste to see a working table in your project, I will advise that you read along for a while. This is because listing 3 has some limitations which we need to address, although it will be able to generate a table for you.
Preventing Re-Initialisation
Recall that after a react component is rendered, a useEffect
call in the component will execute a function passed as argument. By default, this will happen anytime the component is re-rendered. This means that the table will always be initialised again and again for every re-render of the DataTable
component. This wouldn’t be ideal, especially when table data is loaded from an external server. If the table loads data from an external server, the table will always attempt to connect to the server and load the data again on every re-render of DataTable
component.
It will be best if we initialise the table only when the DataTable
component is first mounted. Then, on subsequent re-rendering of the DataTable
component, we will want to prevent the table data from being reloaded. To do this, we will need to pass an empty array as the second argument to useEffect
hook call. This is shown in the following code:
import React, { useEffect, useRef } from "react";
import DataTables from "datatables.net-bs5";
// A component to generate table
const DataTable = (props) => {
// a reference to the table element
const tableRef = useRef(null);
// After component is mounted, initialise the table
useEffect(() => {
// initialise table with DataTables library and return API
const tableApi = new DataTables(tableRef, {...props});
}, []);
// return UI for this component
return (
<div className="table-responsive" style={{padding: '12px'}}>
<table
className="table table-bordered table-hover no-wrap"
ref={tableRef}
style={{width: '100%'}}
>
</table>
</div>
)
}
export default DataTable;
On line 13, in listing 4, we provide an empty array as the second argument to the useEffect
hook call. This will prevent the table from being initialised again anytime the DataTable
component is re-rendered. To understand more about the the useEffect
hook and its conditional execution, you can take a look at the discussion on understanding useEffect hook.
Performing Cleanup
If you remember how we initialised our table with useEffect
hook, you will agree that our table was generated outside the realm of React library. We waited for React to mount the DataTable
component, after which we explicitly generated the table without React. In this sense, we should undo any initialisations we performed when React is about to unmount our component.
Just as useEffect
hook runs a function after a component is mounted, it can also run another function when the component is about to be unmounted from the DOM. This is the time we perform cleanups.
To inform React about the function that needs to be called for side effects cleanup, we specify the function as the return value in the useEffect
hook call. The following code extends listing 4 to include a side effects cleanup function:
import React, { useEffect, useRef } from "react";
import DataTables from "datatables.net-bs5";
// A component to generate table
const DataTable = (props) => {
// a reference to the table element
const tableRef = useRef(null);
// After component is mounted, initialise the table
useEffect(() => {
// initialise table with DataTables library and return API
const tableApi = new DataTables(tableRef, {...props});
// return the function that needs to be called to
// perform clean-up before the component unmounts
return () => tableApi.destroy();
}, []);
// return UI for this component
return (
<div className="table-responsive" style={{padding: '12px'}}>
<table
className="table table-bordered table-hover no-wrap"
ref={tableRef}
style={{width: '100%'}}
>
</table>
</div>
)
}
export default DataTable;
Observe from l
ine 16 that the return value in the useEffect
hook call is an anonymous function. This function will be called before the DataTable
component unmounts from the browser DOM. When called, the function will free up resources with a call to destroy()
method of the DataTables library API object.
We now have a component that we can use to generate a table in react application. There is one last addition we will do to the DataTable component to make it complete. For now, we will leave DataTabl
e component as it is and come back to make a final addition.
Using the DataTable Component
We are now ready to see a working example of our DataTable
component. To generate the table, we will need to define the table columns as well as the data to be populated. We will populate the table with static data, although we can configure it to load data from an external server. If you have worked with DataTables library before, then you know we will need table options data.
From the file explorer pane, click on App.js file. If you used create-react-app to generate the starter application files, then the content of App.js file should read similar to the following:
We do not need most parts of the content of App.js file generated by create-react-app. Delete the code inside the return parenthesis, that is, from line 6 to line 21. Also you should delete the existing imports from line 1 to line 2. The content of App.js file should read similar to the following code listing:
function App() {
return (
)
}
export default App;
We should now import our DataTable
component in App.js file. If you installed DataTables package with Bootstrap 5 styling, then you will need to import Bootstrap CSS files as well.. The following code listing is our updated App.js file:
import React from 'react';
import DataTable from './DataTable';
// import bootstrap CSS file if DataTables for
// Bootstrap package (datatables.net-bs5) was installed.
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
return (
<div className='container'>
<div className='row mt-5 mb-3'>
<div className='col-12'>
<h2>Using DataTables in React Application</h2>
</div>
</div>
<hr />
</div>
)
}
export default App;
Although Listing 7 imports our DataTable
component, it does not use it yet. We need to define table options which will be passed as props
to our table component. In this simple example, we will define data for the columns
and data
options of the DataTable library. It will be helpful if we create a new file from which we will import the table options data.
Create a new javascript file in the src directory and name it tableOptions.js. Click on the tableOptions.js file you created, and from the editor view, type or paste the following:
// define columns for the table
export const tableColumns = [
{data: 'fname', title: 'First Name', className: 'first-name'},
{data: 'lname', title: 'Last Name', className: 'last-name'},
{data: 'email', title: 'Email Address', className: 'email'},
{data: 'gender', title: 'Gender', className: 'gender'}
];
// define static data for the rows
export const tableData = [
{
'fname': 'Daniel',
'lname': 'Oppong',
'email': 'dan@mail.com',
'gender': 'Male'
},
{
'fname': 'Sheila',
'lname': 'Appiah',
'email': 'sheila@mail.com',
'gender': 'Female'
},
{
'fname': 'Emelia',
'lname': 'Cage',
'email': 'cage@email.com',
'gender': 'Female'
}
];
We are only keeping our table data separate from the App
component code. To make tableColumns
and tableData
variables available to our App
component code, we precede their declarations with the export
keyword.
With our table options data ready, we should import them into our App
component code and generate the table.
import React from 'react';
import DataTable from './DataTable';
// import bootstrap CSS file
import 'bootstrap/dist/css/bootstrap.min.css';
// import the table options data
import { tableColumns, tableData } from './tableOptions';
function App() {
return (
<div className='container'>
<div className='row mt-5 mb-3'>
<div className='col-12'>
<h2>Using DataTables in React Application</h2>
</div>
</div>
<hr />
<DataTable
ordering={true}
columns={tableColumns}
data={tableData}
/>
</div>
)
}
export default App;
Line 8 imports tableColumns
and tableData
from tableOptions.js file. If you are already familiar with DataTables library, then you know there are various ways to specify the source of the table data. In this case, we use static table data.
We see the use of our DataTable
component from line 20 to line 24 and pass tableColumns
and tableData
as values to columns
and data
props respectively. We have also included ordering
option of DataTables library as prop and set its value to true
..
To test it, run the application with npm start
command. This should produce the following in your browser window:
Working with DataTables API
Sometimes, it becomes necessary to perform manipulations to the generated table data. For example, we may wish to add, delete, or update some row data. Such operations require that we get a reference to the DataTables API object and use it to perform these operations.
In DataTable
react component, we called an anonymouns function in the useEffect
hook with following code:
useEffect(() => {
// initialise table with DataTables library and return API
const tableApi = new DataTables(tableRef, {...props});
});
The variable tableApi
is a reference to the DataTables API. To later work with the generated table data, we need access to this DataTables API object. When using our DataTable
react component, we can pass a function that needs to be called with this API object as a prop
.
Suppose our DataTable
react component has a prop named onInit
set to a callback function that should be passed the DataTables API object. We can call the callback function with the tableApi
variable as demonstrated in the following code:
import React, { useEffect, useRef } from "react";
import DataTables from "datatables.net-bs5";
// A component to generate table
const DataTable = (props) => {
// a reference to the table element
const tableRef = useRef(null);
// After component is mounted, initialise the table
useEffect(() => {
// initialise table with DataTables library and return API
const tableApi = new DataTables(tableRef, {...props});
// check if function is specicified to receive table API
if (props.onInit && typeof props.onInit == 'function') {
props.onInit(tableApi);
}
// return the function that needs to be called to
// perform clean-up before the component unmounts
return () => tableApi.destroy();
}, []);
// return UI for this component
return (
<div className="table-responsive" style={{padding: '12px'}}>
<table
className="table table-bordered table-hover no-wrap"
ref={tableRef}
style={{width: '100%'}}
>
</table>
</div>
)
}
export default DataTable;
On line 16, we pass tableApi
to the callback function after checking if it was specified as a prop.
In App.js file, let’s define a variable for the DataTables API object as well as a prop function to be passed the table API.
import React from 'react';
import DataTable from './DataTable';
// import bootstrap CSS file
import 'bootstrap/dist/css/bootstrap.min.css';
// import the table options data
import { tableColumns, tableData } from './tableOptions';
// variable to reference the table API object
let tableApi = null;
function App() {
return (
<div className='container'>
<div className='row mt-5 mb-3'>
<div className='col-12'>
<h2>Using DataTables in React Application</h2>
</div>
</div>
<hr />
<DataTable
ordering={true}
columns={tableColumns}
data={tableData}
onInit={(api) => {
// save reference to the datatable API
tableApi = api;
}}
/>
</div>
)
}
export default App;
On line 11, we declare a tableApi
variable which will later reference the DataTables library API after initialisation. On line 27, we set an onInit
prop to an anonymous function which will be called when the table is initialised. This anonymous function will be passed the DataTables API object.
Now that we have access to the DataTables API object, let’s see an example of how we can perform update to data in the generated table.
Updating Row Data
There are so many things that can be done to generated table data. But let’s consider a very simple example. For each row, we will want to toggle first and last names between upper and normal casing. We will provide a button which, when clicked, toggles the text casing of the names.
First, we need to import useState
and useEffect
hooks from react in our App
component:
import React, {useState, useEffect} from 'react';
Just above the return statement of App
component, include the following code:
function App() {
const [capitalize, setCapitalize] = useState(false);
// Toggle names casing whenever capitalize state variable changes
useEffect(() => {
if (tableApi) {
// iterate over each row in the table
tableApi.rows().every(function() {
const data = this.data();
const tdFirstName = this.node().querySelector('td.first-name');
const tdLastName = this.node().querySelector('td.last-name');
tdFirstName.innerText = capitalize ? data.fname.toUpperCase() : data.fname;
tdLastName.innerText = capitalize ? data.lname.toUpperCase() : data.lname;
});
}
}, [capitalize]);
// return statement here
}
From the above code listing, we define a boolean state variable that will indicate whether we need to capitalize the names or not. Then on line 5, the useEffect hook will be executed whenever the capitalize
state variable changes. The useEffect hook will be executed when capitalize changes because we have set it as a dependency on line 17.
To be able to toggle the names casing, let’s include a button as part of the returned JSX. Just after the <hr />
tag and above the DataTable
custom element, type or paste the following code for the button:
function App() {
// ... code here intentionally left out
return (
<div className='container'>
<div className='row mt-5 mb-3'>
<div className='col-12'>
<h2>Using DataTables in React Application</h2>
</div>
</div>
<hr />
<div className='row'>
<div className='col-12'>
<button
type='button'
className='btn btn-primary'
onClick={() => {
// change casing
setCapitalize((prevState) => !prevState);
}}
>
{capitalize ? 'Normalize' : 'Capitalize'}
</button>
</div>
</div>
<DataTable
ordering={true}
columns={columns}
data={data}
onInit={(api) => {
// save reference to the datatable API
tableApi = api;
}}
/>
</div>
)
}
Line 13 to line 26 introduces the button that will be clicked to toggle the capitalize
state variable.
The following code listing is the complete content of App.js
file.
import React, {useState, useEffect} from 'react';
import DataTable from './DataTable';
// import bootstrap CSS file
import 'bootstrap/dist/css/bootstrap.min.css';
// import the table options data
import { tableColumns, tableData } from './tableOptions';
// variable to reference the table API object
let tableApi = null;
function App() {
const [capitalize, setCapitalize] = useState(false);
// Toggle names casing when state changes
useEffect(() => {
if (tableApi) {
// iterate over each row in the table
tableApi.rows().every(function() {
const data = this.data();
const tdFirstName = this.node().querySelector('td.first-name');
const tdLastName = this.node().querySelector('td.last-name');
tdFirstName.innerText = capitalize ? data.fname.toUpperCase() : data.fname;
tdLastName.innerText = capitalize ? data.lname.toUpperCase() : data.lname;
});
}
}, [capitalize]);
return (
<div className='container'>
<div className='row mt-5 mb-3'>
<div className='col-12'>
<h2>Using DataTables in React Application</h2>
</div>
</div>
<hr />
{/* JSX for the button */}
<div className='row'>
<div className='col-12'>
<button
type='button'
className='btn btn-primary'
onClick={() => {
// change casing
setCapitalize((prevState) => !prevState);
}}
>
{capitalize ? 'Normalize' : 'Capitalize'}
</button>
</div>
</div>
<DataTable
ordering={true}
columns={tableColumns}
data={tableData}
onInit={(api) => {
// save reference to the datatable API
tableApi = api;
}}
/>
</div>
)
}
export default App;
To see it working, execute the npm start
command for a live view in your browser window. In repeated attempts, click on the button and notice how the first and last names toggle from upper case to normal case.
As seen from the demonstration so far, we have been able to use DataTables library in React application. Although DataTables library is widely used with jQuery, our implementation in React application was devoid of jQuery. We have also been able to dynamically manipulate the generated table data using the API object that DataTables library exposes.