Creating a pagination component with React.js

As a React developer its important to master React components. Components play a very important part in React.js. In fact React all react applications are composed of components. You can write your own components, or you can use components that someone else have created. There are components that can be useful across many applications. Some examples are datepicker, custom selects and so on. In this tutorial I will show you how to create a pagination component for React.js. As a base I will be using an app that I created for this post http://jsmegatools.com/2017/12/06/creating-a-simple-app-with-react-js/, its source is on Github: https://github.com/jsmegatools/creating-a-simple-app-with-React.js.

The main page of our application is a user list. It has the following code:

import React, { Component } from 'react';
import './style.css';
import UserListItem from '../UserListItem';
import users from '../../mock_data';
import Pagination from '../Pagination';

// Consider switching to PureComponent, when performance becomes an issue in your app
class UserList extends Component {

  constructor(props) {
    super(props);
    this.state = {
      users: [],
      renderedUsers: [],
      page: 1,
    };
    this.handlePageChange = this.handlePageChange.bind(this);
  }

  handlePageChange(page) {
    const renderedUsers = this.state.users.slice((page - 1) * 2, (page - 1) * 2 + 2);
    // in a real app you could query the specific page from a server user list
    this.setState({ page, renderedUsers });
  }

  componentDidMount() {
    // In a real app we make an http request
    setTimeout(() => {
      this.setState({ users, renderedUsers: users.slice(0, 2), total: users.length });
    })
  }

  render() {
    const { page, total, renderedUsers } = this.state;
    return (
      <div>
        <ul id="user-list">
          {
            renderedUsers.map(user =>
            <li key={user.id} className="user-list-item">
              <UserListItem {...user} />
            </li>)
          }
        </ul>
        <Pagination
          margin={2}
          page={page}
          count={Math.ceil(total / 2)}
          onPageChange={this.handlePageChange}
        />
      </div>
    );
  }
}

export default UserList;

We importing, React, styles, UserListItem (It is responsible for showing a particuar user in a list).

import UserListItem from '../UserListItem';

Then we have a mock_data with a list of users.

import users from '../../mock_data';

And finally the pagination component, which this post is all about.

import Pagination from ‘../Pagination';

In constructor we initialize the state object. It will contain an array of users which should come from a server but in our demo app they come from mock data stored in a file. We have a renderedUsers property, which shows how many users per page to display (In our case it will be 2, but you can set whatever amount you want). And finally we have a current page displayed.

this.state = {
  users: [],
  renderedUsers: [],
  page: 1,
};

The next line is we binding the handlePageChange method of the component, that `this` keyword inside the method refers to the UserList component.

In handlePageChange we have some crazy math going on! Just kidding:) This method gets passed to the Pagination component, which supplies us with the page number and from this number we figure out which users to show on the page. We take the page number ( starts from one) then we subtract 1, because the users array is 0 based. Then we multiply by the number of users per page (in our case 2), and we get an offset into a user array from which to start showing users, in the second argument to slice we add 2 to the offset to get the index at which to stop slicing the array.

Then we passed the calculated data in a state so that component can use it in the render function and pass the page number to the Pagination component as a prop.

In componentDidMount simulate the asynchrony of a real http request (we don’t use the real request, because this is a demo app). We save all users, first 2 users to display, and a total number of users to the component state, to use during the app’s work

In the render method we use renderedUsers state property to render the markup for the displayed users. And we pass the page and total property to the Pagination component, along with handlePageChange method. We also pass the margin property (how many adjacent page buttons to show in the pagination) and a count property (how pages there should be). And that’s it the UserList component, let’s talk about the actual Pagination component. Here is its code:

import React, { Component } from 'react';
import classnames from 'classnames';
import './style.css';

class Pagination extends Component {

  constructor(props) {
    super(props);
    this.state = {};
    this.onPageChange = this.onPageChange.bind(this);
    this.goFirstPage = this.goFirstPage.bind(this);
    this.goLastPage = this.goLastPage.bind(this);
    this.goPrevPage = this.goPrevPage.bind(this);
    this.goNextPage = this.goNextPage.bind(this);
  }

  componentWillReceiveProps(newProps) {
    if (newProps === this.props) return;
   const { margin, page, count } = newProps;
    const startPage = page > margin ? page - margin : 1;
    const endPage = page + margin > count ? count : page + margin;
    this.setState({ startPage, endPage, count });
  }

  onPageChange(event) {
    const index =
      Array.prototype.indexOf.call(event.target.parentNode.children, event.target);
    this.props.onPageChange(index + this.state.startPage);
  }

  goFirstPage() {
    this.props.onPageChange(1);
  }

  goLastPage() {
    this.props.onPageChange(this.state.count);
  }

  goPrevPage() {
    this.props.onPageChange(this.props.page - 1);
  }

  goNextPage() {
    this.props.onPageChange(this.props.page + 1);
  }

  render() {
    const { startPage, endPage, count } = this.state;
    const { page, margin } = this.props;
    const pages = [];
    const firstPage = page - margin > 1 ?
          <div
            className="pagination-button pagination-go-first"
            onClick={this.goFirstPage}
          >1</div> :
          null;
    const lastPage = page + margin < count ?
          <div
            className="pagination-button pagination-go-last"
            onClick={this.goLastPage}
          >{count}</div> :
          null;
    const prevPage = page === 1 ? null :
          <div
            className="pagination-button"
            onClick={this.goPrevPage}
          >prev</div>;
    const nextPage = page === count ? null :
          <div
            className="pagination-button"
            onClick={this.goNextPage}
          >next</div>;
    for (let i = startPage; i <= endPage; i++) {
      pages.push(
        <li
          key={i}
          onClick={this.onPageChange}
          className={classnames('pagination-list-item', 'pagination-button', {
            active: i === this.props.page
          })}
        >
          {i}
        </li>
      );
    }

    return (
      <div id="pagination-container">
        <div id="pagination">
          {prevPage}
          {firstPage}
          <ul id="pagination-list">
            {pages}
          </ul>
          {lastPage}
          {nextPage}
        </div>
      </div>
    );
  }
}

export default Pagination;

The first thing that should be talked about is classnames library. It a utility for conditionally joining class names together. You can pass classes that need to be joined as arguments to this function and it will return a space separated list of classes as a string. Suppose some class should be in a list depending on some condition, to conditionally add that class to the list you would pass an object of the following form { className: condition } to the function. Where condition is something that evaluates to true of false. You can have that object have several properties separated by comma that represent several conditional classes, and classnames will return you a string after evaluating all those conditions.

The component has several methods. The lifecycle method componentWillReceiveProps has an important logic within it. We calculate the starting page and ending page in pagination display. Depending on how many pages we are into pagination and how many page buttons we should display on both sides of the current page, the first page will either be the current page minus margin or the very first page in the app. The analogous logic works for the last page, its either the very last page or the current page + margin. After that we save the startPage, endPage, and count to make use of them in the other methods of the component.

In the render method we have a lot of logic that the our component’s final output needs. While in this demo it is ok to keep it in render, if you decide to add more logic to the component, it may be a good idea to move some of the logic out of the render method, to separate the logic and presentation aspects and, if you are going to write unit tests, also improve the testing of the component.

In the render method we create a pages array, which we will populate with the elements that will represent page buttons in the rendered markup. We create a firstPage and lastPage elements, that get rendered when a user navigates far enough away from the first or the last page, so that it is possible to navigate directly to the first or the last page without the need to press the previous or next button many times. We also create a previous and next button conditionally, depending on whether the user is on on the first/last page or on a page which is between these two extremes.

Then using the for loop we create <li> elements that will represent pagination buttons, and populate the pages array. Inside it we use classnames library to mark the current page with active css class.

for (let i = startPage; i <= endPage; i++) {
  pages.push(
    <li
      key={i}
      onClick={this.onPageChange}
      className={classnames('pagination-list-item', 'pagination-button', {
        active: i === this.props.page
      })}
    >
      {i}
    </li>
  );
}

The final part of the render method is the return statement with the JSX of the component, in which we use previously defined variables.

The first page, last page, next page, previous page buttons all have click event handlers, that invoke callbacks as methods of this component, and which are bound to the component in the constructor. Those methods’ logic is pretty self explanatory.

The onPageChange method finds the index of <li> item, whose on click handler was triggered, within its parent, using Array.prototype.indexOf, so that the Array method can be used with HTMLCollection of <li> elements. We get the reference to the HTMLCollection thought the children property of the <ul> element:

onPageChange(event) {
  const index =
    Array.prototype.indexOf.call(event.target.parentNode.children, event.target);
  this.props.onPageChange(index + this.state.startPage);
}

That is all for the logic of this simple pagination component.

And these are the styles for the Pagination component:

#pagination-container {
display: flex;
justify-content: center;
}

#pagination {
display: flex;
color: #505050;
}

#pagination-list {
display: flex;
list-style: none;
margin: 0 0 0 10px;
padding: 0;
}

.pagination-button {
display: flex;
height: 50px;
width: 50px;
border: 1px solid #777;
justify-content: center;
align-items: center;
cursor: pointer;
}

.pagination-list-item {
margin-right: 10px;
}

.pagination-list-item.active {
background: #505050;
color: white;
}

.pagination-go-first {
margin-right: 40px;
position: relative;
margin-left: 10px;
}

.pagination-go-first:before {
position: absolute;
content: '...';
right: -33px;
}

.pagination-go-last {
margin-left: 40px;
position: relative;
margin-right: 10px;
}

.pagination-go-last:before {
position: absolute;
content: '...';
left: -33px;
}

In this tutorial I showed you how to create a pagination component for React. Feel free to use it in your projects. You can find an example application that uses this pagination on Github: https://github.com/jsmegatools/Creating-a-pagination-component-with-React.js