Server-side paging and sorting in React JS



Introduction

This is the 3rd article of the series "React.js with asp.net MVC application".
In the previous post, I have shown you Displaying tabular data from database in react js Today I am going to show you server-side paging and sorting  using React.js, ASP.NET MVC and entity framework.

In the previous article, we fetched all the data from a server at once which can be a performance issue fetching a large amount of data from server side at once. To resolve this performance issue, here in this article I will show you how to implement server-side paging and sorting in React JS.


Here We will fetch a list of employee data (JSON data) from a database via ajax and then we will create React components for displaying the employee data list in the tabular format with server-side paging and sorting.

Just follow the following steps in order to implement Datatables server-side paging, sorting and filtering in React JS.

[Note: If you already visited my previous article and implemented the same, you can start from step - 7.]
Here In this article, I have used Visual Studio 2013

Step - 1: Create New Project.

Go to File > New > Project > ASP.NET  Web Application (under web) > Entry Application Name > Click OK > Select Empty template > Checked MVC (under "Add folders and core references for" option) > OK

Step-2: Add a Database.

Go to Solution Explorer > Right Click on App_Data folder > Add > New item > Select SQL Server Database Under Data > Enter Database name > Add.

Step-3: Create a table for store data.

Open Database > Right Click on Table > Add New Table > Add Columns > Save > Enter table name > Ok.

In this example, I have used a table as below


Employee Table 

Step-4: Add Entity Data Model.

Go to Solution Explorer > Right Click on Project name form Solution Explorer > Add > New item > Select ADO.net Entity Data Model under data > Enter model name > Add.
A popup window will come (Entity Data Model Wizard) > Select Generate from database > Next >
Chose your data connection > select your database > next > Select tables > enter Model Namespace > Finish.

Step-5: Create an MVC Controller.

Go to Solution Explorer > Right Click on Controllers folder form Solution Explorer > Add > Controller > Enter Controller name > Select Templete "empty MVC Controller"> Add.

Here I have created a controller named "HomeController"

Step-6: Add new action into your controller for getting the view, where we will implement our ReactJS component. 

Here I have added "Index" Action into "Home" Controller. Please write this following code
public ActionResult Index()
{
    return View();
}

Step-7: Add view for your Action and design.

Right Click on Action Method (here right click on Index action) > Add View... > Enter View Name > Select View Engine (Razor) > Add.
HTML Code
@{
    ViewBag.Title = "Index";
}

<h2>Show tabular data from database in React JS with server-side paging and sorting</h2>

@* HTML *@
<div class="container" id="griddata">
    
</div>

@* CSS *@
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet" />
@* Jquery *@
<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
@* ReactJS library *@
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react-dom.js"></script>
@* JSX Parser *@
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
@* ReactJS component *@

<script type="text/babel">
    @* Here we will create React component *@

    @* 1. Paging component *@
    var GridPager = React.createClass({
        render : function(){
            var li = [];
            var pageCount = this.props.Size;
            for(var i = 1; i <=pageCount; i++){
                if(this.props.currentPage == i){
                    li.push(<li key={i} className="active"><a href="#">{i}</a></li>);
                }
                else{
                    li.push(<li key={i} ><a href="#" onClick={this.props.onPageChanged.bind(null,i)}>{i}</a></li>);
                }
            }
            return (<ul className="pagination">{li}</ul>);
        }
    });
   
    
    @* 2. Table row component *@
   var EmployeeGridRow = React.createClass({
        render : function(){
            return (
                <tr>
                    <td>{this.props.item.FirstName}</td>
                    <td>{this.props.item.LastName}</td>
                    <td>{this.props.item.EmailID}</td>
                    <td>{this.props.item.Country}</td>
                    <td>{this.props.item.City}</td>
                </tr>
            );
        }
    });
    @* 3. Table component *@
    var EmployeeGridTable = React.createClass({
        getInitialState : function(){
            return {
                Data : {
                    List : [],
                    totalPage : 0,
                    sortColumnName : null,
                    sortOrder : null,
                    currentPage : 1,
                    pageSize : 3
                }
            }
        },
        componentDidMount : function(){
            this.populateData();
        },
        @* function for populate data *@
        populateData: function(){
            var params = {
                pageSize : this.state.Data.pageSize,
                currentPage : this.state.Data.currentPage
            }
            if(this.state.Data.sortColumnName){
                params.sortColumnName = this.state.Data.sortColumnName;
            }
            if(this.state.Data.sortOrder){
                params.sortOrder = this.state.Data.sortOrder;
            }

            $.ajax({
                url : this.props.dataUrl,
                type : 'GET',
                data : params,
                success : function(data){
                    if(this.isMounted()){
                        this.setState({
                            Data : data
                        });
                    }
                }.bind(this),
                error: function(err){
                    alert('Error');
                }.bind(this)
            });
        },
        @* function for pagination *@
        pageChanged:function(pageNumber,e){
            e.preventDefault();
            this.state.Data.currentPage = pageNumber;
            this.populateData();
        },
        @* function for sorting *@
        sortChanged : function(sortColumnName, order , e){
            e.preventDefault();
            this.state.Data.sortColumnName = sortColumnName;
            this.state.Data.currentPage = 1;
            this.state.Data.sortOrder = order.toString().toLowerCase() == 'asc' ? 'desc':'asc';
            this.populateData();
        },
        @* function for set sort icon on table header *@
        _sortClass : function(filterName){
            return "fa fa-fw " + ((filterName == this.state.Data.sortColumnName) ? ("fa-sort-" + this.state.Data.sortOrder) : "fa-sort");
        },
        @* render *@
        render : function(){
            var rows = [];
            this.state.Data.List.forEach(function(item){
                rows.push(<EmployeeGridRow key={item.EmployeeID} item={item}/>);
            });
            return (
                <div>
                    <table className="table table-responsive table-bordered">
                        <thead>
                            <tr>
                                <th onClick={this.sortChanged.bind(this,'FirstName',this.state.Data.sortOrder)}>First Name
                                    <i className={this._sortClass('FirstName')}></i></th>
                                <th onClick={this.sortChanged.bind(this,'LastName',this.state.Data.sortOrder)}>
                                    Last Name
                                    <i className={this._sortClass('LastName')}></i></th>
                                <th onClick={this.sortChanged.bind(this,'EmailID',this.state.Data.sortOrder)}>
                                    Email
                                    <i className={this._sortClass('EmailID')}></i>
                                </th>
                                <th onClick={this.sortChanged.bind(this,'Country',this.state.Data.sortOrder)}>
                                    Country
                                    <i className={this._sortClass('Country')}></i>
                                </th>
                                <th onClick={this.sortChanged.bind(this,'City',this.state.Data.sortOrder)}>
                                    City
                                    <i className={this._sortClass('City')}></i>
                                </th>
                            </tr>
                        </thead>
                        <tbody>{rows}</tbody>
                    </table>
                    <GridPager Size={this.state.Data.totalPage} onPageChanged={this.pageChanged} currentPage={this.state.Data.currentPage}/>
                </div>
            );
        }
    });
    ReactDOM.render(<EmployeeGridTable dataUrl="/home/getEmployeeList"/>, document.getElementById('griddata'));
</script>
<style>
    #griddata th
    {
        cursor: pointer;
    }
    .fa
    {
        float: right;
        margin-top: 4px;
    }
</style>

If you see, here I have created three React components named GridPager (for generating paging links), EmployeeGridTable (for generating the table, where data will be displayed) and EmployeeGridRow (for generating table rows with data item).

Also, I have added functions populateData (for fetching data from the server), pageChanged (paging functionality), sortChanged (for sorting event) and _sortClass (for adding sort icon on table header column).


Step-8: Add reference of System.Linq.Dynamic

Here I have added System.Linq.Dynamic reference from NuGet packages
Go to Solution Explorer > Right click on References > Manage NuGet packages > Search with "System.Linq.Dynamic" > Install.

Step-9: Add another action to your controller for return  data(employee) list as JSON Data

Here I have used "GetEmployeeData" Action for fetch data. Please write this following code
public JsonResult getEmployeeList(string sortColumnName = "FirstName", string sortOrder = "asc", int pageSize=3, int currentPage=1)
{
    List<Employee> List = new List<Employee>();
    int totalPage = 0;
    int totalRecord = 0;

    using (MyDatabaseEntities dc = new MyDatabaseEntities())
    {
        var emp = dc.Employees;
        totalRecord = emp.Count();
        if (pageSize > 0)
        {
            totalPage = totalRecord / pageSize + ((totalRecord % pageSize) > 0 ? 1 : 0);
            List = emp.OrderBy(sortColumnName + " " + sortOrder).Skip(pageSize * (currentPage - 1)).Take(pageSize).ToList();
        }
        else
        {
            List = emp.ToList();
        }
    }

    return new JsonResult
    {
        //Data = new { List = List, totalPage = totalPage, sortColumnName = sortColumnName, sortOrder = sortOrder, currentPage = currentPage},
        Data = new { List = List, totalPage = totalPage, sortColumnName = sortColumnName, sortOrder = sortOrder, currentPage = currentPage, pageSize = pageSize },
        JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
}
[Note: you can see the line 24 (yellow marked), I have updated this line on 19th feb 2016. Added a missing parameter pageSize]

Step-10: Run Application.

[Note: Here I have updated the code for add search box. You just need to update 2 steps code of this article, step-7 and step-9. Here I have marked updated lines with yellow background.] download
Updated step-7
@{
    ViewBag.Title = "Index";
}

<h2>Show tabular data from database in React JS with server-side paging and sorting</h2>

@* HTML *@
<div class="container" id="griddata">
    
</div>

@* CSS *@
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet" />
@* Jquery *@
<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
@* ReactJS library *@
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react-dom.js"></script>
@* JSX Parser *@
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
@* ReactJS component *@

<script type="text/babel">
    @* Here we will create React component *@
    @* New : Search box component *@
    var SearchBox = React.createClass({
        handleChange : function(e){
            console.log('search');
            this.props.onSearchChanged(e.target.value);
        },
        render: function(){
            return (
                <div style={{marginBottom:15+'px',float:'right'}} ><input type="text" value={this.props.searchText} placeholder="search" onChange={this.handleChange}/></div>
            )
        }
    });
    @* 1. Paging component *@
    var GridPager = React.createClass({
        render : function(){
            var li = [];
            var pageCount = this.props.Size;
            for(var i = 1; i <=pageCount; i++){
                if(this.props.currentPage == i){
                    li.push(<li key={i} className="active"><a href="#">{i}</a></li>);
                }
                else{
                    li.push(<li key={i} ><a href="#" onClick={this.props.onPageChanged.bind(null,i)}>{i}</a></li>);
                }
            }
            return (<ul className="pagination">{li}</ul>);
        }
    });
   
    
    @* 2. Table row component *@
   var EmployeeGridRow = React.createClass({
        render : function(){
            return (
                <tr>
                    <td>{this.props.item.FirstName}</td>
                    <td>{this.props.item.LastName}</td>
                    <td>{this.props.item.EmailID}</td>
                    <td>{this.props.item.Country}</td>
                    <td>{this.props.item.City}</td>
                </tr>
            );
        }
    });
    @* 3. Table component *@
    var EmployeeGridTable = React.createClass({
        getInitialState : function(){
            return {
                Data : {
                    List : [],
                    totalPage : 0,
                    sortColumnName : null,
                    sortOrder : null,
                    currentPage : 1,
                    pageSize : 3,
                    searchText:''
                }
            }
        },
        componentDidMount : function(){
            this.populateData();
        },
        @* function for populate data *@
        populateData: function(){
            var params = {
                pageSize : this.state.Data.pageSize,
                currentPage : this.state.Data.currentPage,
                searchText : this.state.Data.searchText
            }
            if(this.state.Data.sortColumnName){
                params.sortColumnName = this.state.Data.sortColumnName;
            }
            if(this.state.Data.sortOrder){
                params.sortOrder = this.state.Data.sortOrder;
            }

            $.ajax({
                url : this.props.dataUrl,
                type : 'GET',
                data : params,
                success : function(data){
                    if(this.isMounted()){
                        this.setState({
                            Data : data
                        });
                    }
                }.bind(this),
                error: function(err){
                    alert('Error');
                }.bind(this)
            });
        },
        @* function for pagination *@
        pageChanged:function(pageNumber,e){
            e.preventDefault();
            this.state.Data.currentPage = pageNumber;
            this.populateData();
        },
        @* function for sorting *@
        sortChanged : function(sortColumnName, order , e){
            e.preventDefault();
            this.state.Data.sortColumnName = sortColumnName;
            this.state.Data.currentPage = 1;
            this.state.Data.sortOrder = order.toString().toLowerCase() == 'asc' ? 'desc':'asc';
            this.populateData();
        },
        searchChange : function(value){
            var d = this.state.Data;            
            d.searchText = value;
            this.setState({
                Data : d            
            });
            this.populateData();
        },
        @* function for set sort icon on table header *@
        _sortClass : function(filterName){
            return "fa fa-fw " + ((filterName == this.state.Data.sortColumnName) ? ("fa-sort-" + this.state.Data.sortOrder) : "fa-sort");
        },
        @* render *@
        render : function(){
            var rows = [];
            this.state.Data.List.forEach(function(item){
                rows.push(<EmployeeGridRow key={item.EmployeeID} item={item}/>);
            });
            return (
                <div>
                    <SearchBox onSearchChanged={this.searchChange} searchText={this.state.Data.searchText}/>
                    <table className="table table-responsive table-bordered">
                        <thead>
                            <tr>
                                <th onClick={this.sortChanged.bind(this,'FirstName',this.state.Data.sortOrder)}>First Name
                                    <i className={this._sortClass('FirstName')}></i></th>
                                <th onClick={this.sortChanged.bind(this,'LastName',this.state.Data.sortOrder)}>
                                    Last Name
                                    <i className={this._sortClass('LastName')}></i></th>
                                <th onClick={this.sortChanged.bind(this,'EmailID',this.state.Data.sortOrder)}>
                                    Email
                                    <i className={this._sortClass('EmailID')}></i>
                                </th>
                                <th onClick={this.sortChanged.bind(this,'Country',this.state.Data.sortOrder)}>
                                    Country
                                    <i className={this._sortClass('Country')}></i>
                                </th>
                                <th onClick={this.sortChanged.bind(this,'City',this.state.Data.sortOrder)}>
                                    City
                                    <i className={this._sortClass('City')}></i>
                                </th>
                            </tr>
                        </thead>
                        <tbody>{rows}</tbody>
                    </table>
                    <GridPager Size={this.state.Data.totalPage} onPageChanged={this.pageChanged} currentPage={this.state.Data.currentPage}/>
                </div>
            );
        }
    });
    ReactDOM.render(<EmployeeGridTable dataUrl="/home/getEmployeeList"/>, document.getElementById('griddata'));
</script>
Updated step-9
public JsonResult getEmployeeList(string sortColumnName = "FirstName", string sortOrder = "asc", int pageSize = 3, int currentPage = 1, string searchText = "")
{
    List<Employee> List = new List<Employee>();
    int totalPage = 0;
    int totalRecord = 0;

    using (MyDatabaseEntities dc = new MyDatabaseEntities())
    {
        var emp = dc.Employees.Select(a => a);
        //Search
        if (!string.IsNullOrEmpty(searchText))
        {
            emp = emp.Where(a => a.FirstName.Contains(searchText) || a.LastName.Contains(searchText) || a.EmailID.Contains(searchText) || a.City.Contains(searchText) || a.Country.Contains(searchText));
        }
        totalRecord = emp.Count();
        if (pageSize > 0)
        {
            totalPage = totalRecord / pageSize + ((totalRecord % pageSize) > 0 ? 1 : 0);
            List = emp.OrderBy(sortColumnName + " " + sortOrder).Skip(pageSize * (currentPage - 1)).Take(pageSize).ToList();
        }
        else
        {
            List = emp.ToList();
        }
    }

    return new JsonResult
    {
        //Data = new { List = List, totalPage = totalPage, sortColumnName = sortColumnName, sortOrder = sortOrder, currentPage = currentPage},
        Data = new { List = List, totalPage = totalPage, sortColumnName = sortColumnName, sortOrder = sortOrder, currentPage = currentPage, pageSize = pageSize, searchText = searchText },
        JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
}

Live Demo  Download


Hello ! My name is Sourav Mondal. I am a software developer working in Microsoft .NET technologies since 2010.

I like to share my working experience, research and knowledge through my site.

I love developing applications in Microsoft Technologies including Asp.Net webforms, mvc, winforms, c#.net, sql server, entity framework, Ajax, Jquery, web api, web service and more.