How to use DataTables in React Application


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:

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

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

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

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

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

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

JavaScript
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 line 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 DataTable 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:

app.js-default-content

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:

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

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

JavaScript
// 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.

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

react datatable

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:

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

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

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

JavaScript
import React, {useState, useEffect} from 'react';

Just above the return statement of App component, include the following code:

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

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

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

react-datatable-update

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.


Leave a Reply

Your email address will not be published. Required fields are marked *