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.

13 comments on “Tutorial: Sorting and Filtering a ReactJS Datatable

  1. Steve

    Hey,

    Great work! Any idea how to make this sort based on a Tag system? I develop for ecommerce generally, and will like to hear any suggestions.

    Steve

  2. Alfre

    Hi friend,

    Great post! I’m new to reactjs. I have a problem whet set renderHeader property in Colum, I get “commons.chunk.js:4667 Uncaught TypeError: Cannot read property ‘hasOwnProperty’ of undefined” why?

    Regards.

  3. Irene

    Thanks for the tutorial.

    For the filter part, input fields aren’t generating on my table.
    This code doesn’t seem to be calling the renderHeader function…

  4. abrahm

    Hey, I have tried to implement same code, but i don’t know whether i’m making mistake or not ,code is not compiling. If anyone have written the working code, please help me.

  5. Mark

    where did the JSON data go?
    ” this.rows = //json data”
    completed code would be nice. It is not compiling for me either.

Leave a Reply

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

Obs: Use the tag <pre lang="LANGUAGE"> to include code blocks to your comment.
Example: <pre lang="javascript"> console.log('Test'); </pre>