Tutorial: Sorting and Filtering a ReactJS Datatable

ReactJS Datatable with Sort and Filter
ReactJS Datatable with Sort and Filter

A few months ago I’ve made this post about how to create a reactjs datatable using the FixedDataTable module, I didn’t cover much, I just gave a simple example explaining how to populate the table with JSON data. Today we’re going a little further, continuing from where I stopped I’m going to show you how to sort and filter the data on this datatable.

Before we begin I’ll reorganize the code a little bit, let’s put all our datatable related code in a different file, I’ll name it myTable.js, its content should look like this:

import React from 'react';
import {Table, Column, Cell} from 'fixed-data-table';
 
class MyTable extends React.Component {
 
  constructor(props) {
    super(props);
    this.state = {
      rows : [{"id":1,"first_name":"William","last_name":"Elliott","email":"welliott0@wisc.edu",
             "country":"Argentina","ip_address":"247.180.226.89"},
              {"id":2,"first_name":"Carl","last_name":"Ross","email":"cross1@mlb.com",
             "country":"South Africa","ip_address":"27.146.70.36"},
              {"id":3,"first_name":"Jeremy","last_name":"Scott","email":"jscott2@cbsnews.com",
             "country":"Colombia","ip_address":"103.52.74.225"},
             // more data
    };
  }
 
  render() {
      return <Table
        height={40+((this.state.rows.length+1) * 30)}
        width={1150}
        rowsCount={this.state.rows.length}
        rowHeight={30}
        headerHeight={30}
        rowGetter={function(rowIndex) {return this.state.rows[rowIndex]; }.bind(this)}>
        <Column dataKey="id" width={50} label="Id" />
        <Column dataKey="first_name" width={200} label="First Name" />
        <Column  dataKey="last_name" width={200} label="Last Name" />
        <Column  dataKey="email" width={400} label="e-mail" />
        <Column  dataKey="country" width={300} label="Country" />
      </Table>;
  }
}
 
module.exports = MyTable;

I’m just making it because it’s a good practice, in this example it won’t make much difference, but in an application with multiple components it would get really messy if you put everything on the main.js.

After isolating the datatable component our main.js is much cleaner:

import MyTable from './myTable';
import React from 'react';
import ReactDOM from 'react-dom';
 
ReactDOM.render(<MyTable/>,document.getElementById('example'));

If you run the project now you should see the exact same result as before, now let’s start adding some features to this table.

Filtering the data

We have to make a few changes in the constructor in order to add the filter functionality:

constructor(props) {
  super(props);
  this.rows = //json data
  this.state = {
    filteredDataList: this.rows
  };
}

As you can see I took the rows variable out of the state and replaced it with filteredDataList, now you must think of rows as our data source, like a database table or a web service it shouldn’t be part of our state, what should be is the data we are actually displaying on the table, which will be the filteredDataList. Initially it will have the exact same data as the rows variable, but as the user starts filtering, its value will change.

After this, the attributes height, rowsCount and rowGetter of our table are still referencing the rows variable, as the table won’t interact directly with this variable anymore, they should be changed to reference the filteredDataList.

Now let’s add to our class the methods responsible for the filtering:

_renderHeader(label, cellDataKey) {
  return <div>
        <span>{label}</span>
          <div>
            <br />
            <input style={{width:90+'%'}} onChange={this._onFilterChange.bind(this, cellDataKey)}/>
          </div>
      </div>;
}
 
_onFilterChange(cellDataKey, event) {
  if (!event.target.value) {
    this.setState({
      filteredDataList: this.rows,
    });
  }
  var filterBy = event.target.value.toString().toLowerCase();
  var size = this.rows.length;
  var filteredList = [];
  for (var index = 0; index < size; index++) {
    var v = this.rows[index][cellDataKey];
    if (v.toString().toLowerCase().indexOf(filterBy) !== -1) {
      filteredList.push(this.rows[index]);
    }
  }
  this.setState({
    filteredDataList: filteredList,
  });
}

The _renderHeader() function tells the Column what should be rendered on it’s header, in this case it’s just adding an input so the user can type and filter the data, on its onChange event I’m calling the function _onFilterChange which will actually do the filtering and update the filteredDataList value.

Lastly we have to call the _renderHeader function on our columns like this:

<Column dataKey="id" width={50} label="id"  
     headerRenderer={this._renderHeader.bind(this)}/>

Note that I’ve used bind() to call the function _renderHeader(), it’s really important that you do exactly like that, otherwise you will come across the problem described here.

Now you can run your project, if you did everything right you should see an input bellow the label on the header, if you type something in there you’ll see that the filter is already working.

Making the Columns Sortable

In order to use the sort feature we have to add two more attributes to the state: sortBy and sortDir:

 
constructor(props) {
  super(props);
  this.rows = //json data
  this.state = {
    filteredDataList: this.rows,
    sortBy: 'id',
    sortDir: null
  };
}

The first is responsible for telling by which column our datatable is currently being sorted by, it’s being initialized with the column id, and the second is just for allowing us to control whether it’s ASC or DESC.

We also have to modify the _renderHeader() a little bit:

_renderHeader(label, cellDataKey) {
  return <div>
        <a onClick={this._sortRowsBy.bind(this, cellDataKey)}>{label}</a>
          <div>
            <br />
            <input style={{width:90+'%'}} onChange={this._onFilterChange.bind(this, cellDataKey)}/>
          </div>
      </div>;
}

I just replaced the span with a link where the user will click to sort the table, on the onClick event it calls the following function:

_sortRowsBy(cellDataKey) {
  var sortDir = this.state.sortDir;
  var sortBy = cellDataKey;
  if (sortBy === this.state.sortBy) {
    sortDir = this.state.sortDir === 'ASC' ? 'DESC' : 'ASC';
  } else {
    sortDir = 'DESC';
  }
  var rows = this.state.filteredDataList.slice();
  rows.sort((a, b) => {
    var sortVal = 0;
    if (a[sortBy] > b[sortBy]) {
      sortVal = 1;
    }
    if (a[sortBy] < b[sortBy]) {
      sortVal = -1;
    }
 
    if (sortDir === 'DESC') {
      sortVal = sortVal * -1;
    }
    return sortVal;
  });
 
  this.setState({sortBy, sortDir, filteredDataList : rows});
}

This function is responsible for the sorting, if you take a look at the code you’ll see that in order to “decide” how the table should be sorted it uses and modifies the two new variables we added to the state, at the end it sets the state again with the modified values.

Now add this code to your render() function, right before returning your component:

var sortDirArrow = '';
if (this.state.sortDir !== null){
  sortDirArrow = this.state.sortDir === 'DESC' ? ' ↓' : ' ↑';
}

This code is just for controlling the arrow that will be displayed in front of the column label. Don’t forget to add the logic to your column label attribute as well, otherwise the arrow won’t be displayed.

<Column dataKey="id" width={50} 
  label={'id' + (this.state.sortBy === 'id' ? sortDirArrow : '')}
  headerRenderer={this._renderHeader.bind(this)}/>

Ok, now we are done with the sorting and filtering, if you run your project the result should be exactly the same as the gif at the beginning of this post.

That’s it! Hope you enjoyed the tutorial, just leave a comment if you have any doubts.

ReactJS DataTable with Sort, Filter and Pagination Example (PART 1)

fixed-data-table home page
fixed-data-table home page

Today we’re going to learn how to create a simple datatable for react using the FixedDataTable module, since it uses Javascript ES6 code you must have at least some basic knowledge about it. To c ompile our ES6 code we’re going to use Babel and Webpack, since our js files will also contain JSX and React code, besides babel-preset-es2015 we are going to need babel-preset-react as well.

So let’s get started, create your project folder and run npm init to create the package.json file, after that we need to add all the dependencies, the following code has everything we need, just copy and paste it in there:

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.3.17",
    "babel-loader": "^6.2.0",
    "babel-preset-es2015": "^6.3.13",
    "babel-preset-react": "^6.3.13",
    "babel-runtime": "^6.3.19",
    "webpack": "^1.12.9"
  },
  "dependencies": {
    "fixed-data-table": "^0.6.0",
    "react": "^0.14.3",
    "react-dom": "^0.14.3"
  }
}

As devDependencies(dependencies we need only for development) we have webpack and some babel modules, and as regular dependencies we’ll only need react and the fixed-data-table module. Now run npm install, it’ll download and install all the dependencies, it may take a while.

The next step is to configure the webpack module, to do that we need to create the webpack.config.js file and add the following code to it:

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
"use strict";
 
module.exports = {
  entry: './main.js',
  output: { path: __dirname, filename: 'bundle.js' },
 
  module: {
    loaders: [
      {
        test: /.js?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react']
        }
      }
    ]
  },
};

In this file I’m just specifying the files webpack should transform (or transpile) and what loaders it should use to do it. As the entry file we have the main.js, and as the output (the transformed file) we have the bundle.js, both files were not created yet. We’re also saying to webpack that babel-loader will be our loader and that it should test and transform every js file in our project, except the ones inside the node_modules folder. Lastly we are defining es2015 and react as our presets.

OK, now we’re ready to write some code, first let’s create the main.js file with the following code:

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React from 'react';
import ReactDOM from 'react-dom';
import {Table, Column, Cell} from 'fixed-data-table';
 
const rows = [{"id":1,"first_name":"William","last_name":"Elliott","email":"welliott0@wisc.edu",
"country":"Argentina","ip_address":"247.180.226.89"},
{"id":2,"first_name":"Carl","last_name":"Ross","email":"cross1@mlb.com",
"country":"South Africa","ip_address":"27.146.70.36"},
{"id":3,"first_name":"Jeremy","last_name":"Scott","email":"jscott2@cbsnews.com",
"country":"Colombia","ip_address":"103.52.74.225"},
 
// more data
];
 
ReactDOM.render(
    <Table
      height={rows.length * 30}
      width={1150}
      rowsCount={rows.length}
      rowHeight={30}
      headerHeight={30}
      rowGetter={function(rowIndex) {return rows[rowIndex]; }}>
 
      <Column dataKey="id" width={50} label="Id" />
      <Column dataKey="first_name" width={200} label="First Name" />
      <Column  dataKey="last_name" width={200} label="Last Name" />
      <Column  dataKey="email" width={400} label="e-mail" />
      <Column  dataKey="country" width={300} label="Country" />
 
    </Table>,
    document.getElementById('example')
);

This code can already gerenate a basic datatable, but without any features yet (sort, filter and pagination). As you can see I’ve used some JSON data to populate the datatable, but feel free to use your own data if you want. Now if you take a look at the code of our actual table component you’ll see that it’s really easy to understand, almost every attribute of the Table tag is responsible for controlling its dimensions, except the rowGetter, which is responsible for saying where each row should get its data. We should focus on the Column tags, which by far are also very simple, besides the attribute width, we have the dataKey and label, the first one is the name of the JSON field that will be displayed in this column, and the second is the text that will appear in the header.

Now let’s create our HTML:

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>React DataTable</title>
    <link href="node_modules/fixed-data-table/dist/fixed-data-table.css" 
          rel="stylesheet">
  </head>
  <body style="font-family:arial;font-size:12px; background:#e1e1e1">
    <div id="example"></div>
    <script src="bundle.js"></script>
  </body>
</html>

This is just a regular html file, I just added the css from the fixed-data-table module and also the bundle.js, which is the compiled version of our main.js, it’s really important that you put the bundle.js at the end of the body, otherwise it won’t be able to render the datatable.

Now just run the command webpack to generate the bundle.js, if everything goes right you can now open your index.html file on your browser, this should be the result:

ReactJS datatable
ReactJS datatable

Perfect!! Now our datatable can already show the data in the browser, the next step is to start adding some features to this table, like pagination, sorting and filtering, but I’ll leave that to the PART 2 of this tutorial.

That’s it for today!! hope you enjoyed, till the next one!!