Published: March 14 2017
Last updated: April 23 2018

React - Pagination Example with Logic like Google

Built with React 16.3.1

Other versions available:

UPDATE 10 Apr 2018 - The pagination component is now available on npm, for details check out npm - JW React Pagination Component.

This is an example of how to implement pagination in React and ES6 / JavaScript with logic like Google's search results.

Project is available on GitHub at https://github.com/cornflourblue/react-pagination-example.

Here it is in action: (See on CodePen at http://codepen.io/cornflourblue/pen/oZZzLr/)

Update History:

  • 23 Apr 2018 - Added pageSize prop to pagination component
  • 10 Apr 2018 - Updated to React 16.3.1, updated to Webpack 4.5, removed dependency on underscore.js / lodash, published pagination component to npm.
  • 14 Mar 2017 - Built with React 15.4.2

The React code on CodePen is slightly different to the code on GitHub, on CodePen all the React components are in same file / window because CodePen only has a single window for JS, the GitHub repo is structured like a real application with the React components in separate JSX files, this is the better way to structure your React project. Also the example on CodePen transpiles the ES6 into JS in the browser which is only good for demos and not production applications.


Google's Pagination Logic

The logic in Google's pagination is as follows:

  • there are 10 page links shown at any time (e.g. 1 2 3 4 5 6 7 8 9 10) unless there are less than 10 total pages
  • the active link (current page) is in the 6th position, except for when the active link is below 6 or less than 4 from the last position


Here's what it looks like for each page if there are 15 total pages:

[1] 2 3 4 5 6 7 8 9 10
1 [2] 3 4 5 6 7 8 9 10
1 2 [3] 4 5 6 7 8 9 10
1 2 3 [4] 5 6 7 8 9 10
1 2 3 4 [5] 6 7 8 9 10
1 2 3 4 5 [6] 7 8 9 10
2 3 4 5 6 [7] 8 9 10 11
3 4 5 6 7 [8] 9 10 11 12
4 5 6 7 8 [9] 10 11 12 13
5 6 7 8 9 [10] 11 12 13 14
6 7 8 9 10 [11] 12 13 14 15
6 7 8 9 10 11 [12] 13 14 15
6 7 8 9 10 11 12 [13] 14 15
6 7 8 9 10 11 12 13 [14] 15
6 7 8 9 10 11 12 13 14 [15] 


Running the React Pagination Example Locally

The example uses webpack to transpile / compile the ES6 code and build the project, webpack is an npm package that runs on Node.

  1. Install NodeJS and NPM from https://nodejs.org/en/download/.
  2. Download the React project source code from GitHub at  https://github.com/cornflourblue/react-pagination-example
  3. Install all required npm packages by running npm install from the command line in the project root folder (where the package.json is located).
  4. Start the application by running npm start from the command line in the project root folder, this will start the webpack dev server which serves the example from port 8080 on your local machine.
  5. Browse to the url http://localhost:8080 to test the React pagination example


React Pagination Component

The React pagination component is a self contained reusable component that can be used to enable paging through any list of items. As properties it takes the array of items to be paged and an onChangePage() callback function to notify the parent component when the page is changed. It also has an optional property to set the initial page displayed which defaults to the first page, and the page size which defaults to 10.

When the paged items array is updated after initial rendering (e.g. after an async http request to a web api) the pagination component resets the page in the react componentDidUpdate() lifecycle function, causing the pagination component to re-render and notify the parent component via the onChangePage() callback;

I've structured the code according to the recommended best practices on the Airbnb React/JSX Style Guide.

import React from 'react';
import PropTypes from 'prop-types';

const propTypes = {
    items: PropTypes.array.isRequired,
    onChangePage: PropTypes.func.isRequired,
    initialPage: PropTypes.number,
    pageSize: PropTypes.number
}

const defaultProps = {
    initialPage: 1,
    pageSize: 10
}

class Pagination extends React.Component {
    constructor(props) {
        super(props);
        this.state = { pager: {} };
    }

    componentWillMount() {
        // set page if items array isn't empty
        if (this.props.items && this.props.items.length) {
            this.setPage(this.props.initialPage);
        }
    }

    componentDidUpdate(prevProps, prevState) {
        // reset page if items array has changed
        if (this.props.items !== prevProps.items) {
            this.setPage(this.props.initialPage);
        }
    }

    setPage(page) {
        var { items, pageSize } = this.props;
        var pager = this.state.pager;

        if (page < 1 || page > pager.totalPages) {
            return;
        }

        // get new pager object for specified page
        pager = this.getPager(items.length, page, pageSize);

        // get new page of items from items array
        var pageOfItems = items.slice(pager.startIndex, pager.endIndex + 1);

        // update state
        this.setState({ pager: pager });

        // call change page function in parent component
        this.props.onChangePage(pageOfItems);
    }

    getPager(totalItems, currentPage, pageSize) {
        // default to first page
        currentPage = currentPage || 1;

        // default page size is 10
        pageSize = pageSize || 10;

        // calculate total pages
        var totalPages = Math.ceil(totalItems / pageSize);

        var startPage, endPage;
        if (totalPages <= 10) {
            // less than 10 total pages so show all
            startPage = 1;
            endPage = totalPages;
        } else {
            // more than 10 total pages so calculate start and end pages
            if (currentPage <= 6) {
                startPage = 1;
                endPage = 10;
            } else if (currentPage + 4 >= totalPages) {
                startPage = totalPages - 9;
                endPage = totalPages;
            } else {
                startPage = currentPage - 5;
                endPage = currentPage + 4;
            }
        }

        // calculate start and end item indexes
        var startIndex = (currentPage - 1) * pageSize;
        var endIndex = Math.min(startIndex + pageSize - 1, totalItems - 1);

        // create an array of pages to ng-repeat in the pager control
        var pages = [...Array((endPage + 1) - startPage).keys()].map(i => startPage + i);

        // return object with all pager properties required by the view
        return {
            totalItems: totalItems,
            currentPage: currentPage,
            pageSize: pageSize,
            totalPages: totalPages,
            startPage: startPage,
            endPage: endPage,
            startIndex: startIndex,
            endIndex: endIndex,
            pages: pages
        };
    }

    render() {
        var pager = this.state.pager;

        if (!pager.pages || pager.pages.length <= 1) {
            // don't display pager if there is only 1 page
            return null;
        }

        return (
            <ul className="pagination">
                <li className={pager.currentPage === 1 ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(1)}>First</a>
                </li>
                <li className={pager.currentPage === 1 ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.currentPage - 1)}>Previous</a>
                </li>
                {pager.pages.map((page, index) =>
                    <li key={index} className={pager.currentPage === page ? 'active' : ''}>
                        <a onClick={() => this.setPage(page)}>{page}</a>
                    </li>
                )}
                <li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.currentPage + 1)}>Next</a>
                </li>
                <li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.totalPages)}>Last</a>
                </li>
            </ul>
        );
    }
}

Pagination.propTypes = propTypes;
Pagination.defaultProps = defaultProps;
export default Pagination;


React App Component that uses the Pagination Component

An example react component that uses the above pagination component to paginate a list of 150 example items that are generated using the Array object and spread operator.

import React from 'react';
import Pagination from './Pagination';

class App extends React.Component {
    constructor() {
        super();

        // an example array of 150 items to be paged
        var exampleItems = [...Array(150).keys()].map(i => ({ id: (i+1), name: 'Item ' + (i+1) }));

        this.state = {
            exampleItems: exampleItems,
            pageOfItems: []
        };

        // bind function in constructor instead of render (https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md)
        this.onChangePage = this.onChangePage.bind(this);
    }

    onChangePage(pageOfItems) {
        // update state with new page of items
        this.setState({ pageOfItems: pageOfItems });
    }

    render() {
        return (
            <div>
                <div className="container">
                    <div className="text-center">
                        <h1>React - Pagination Example with logic like Google</h1>
                        {this.state.pageOfItems.map(item =>
                            <div key={item.id}>{item.name}</div>
                        )}
                        <Pagination items={this.state.exampleItems} onChangePage={this.onChangePage} />
                    </div>
                </div>
                <hr />
                <div className="credits text-center">
                    <p>
                        <a href="http://jasonwatmore.com" target="_top">JasonWatmore.com</a>
                    </p>
                </div>
            </div>
        );
    }
}

export default App;

 


Need Some React Help?

Search fiverr for freelance React developers.


Follow me for updates

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by