Setting up pagination for a Rails, React and Bootstrap  app
Tags: rails, ruby, react, javascript
Published: 2021-04-02 19:02:48

I recently seeded my app database with data from a public API and I very quickly ran into usability issues once I had a huge amount of data to display on the front-end. So I looked into pagination, everyone uses it, but I hadn't the need when I only had a few hiking tracks to display. But now loading the hiking tracks was taking close to a minute and Firefox would think the website was broken and asking me to close it. Not a good look in a project I was sharing with possible employers!

So I added pagination to my rails API back-end and created my own React and Bootstrap component to navigate through the pages. It didn't take too long, but there wasn't an easy to find article showing all the steps so I thought I should create one.

This is what the final product looks like.
![pagination](https://i.imgur.com/BFLqCYv.jpg)

## Back-end Setup
- We need to install the [will-paginate](https://github.com/mislav/will_paginate) gem, so add `gem 'will_paginate'` to your gemfile and run bundle install.
- The paginate gem adds a few methods to your Class so that it can produce an array of data based on which page you input. Thankfully right out of the box it manages all this itself. But if you wanted to customize, say, the amount per page,  you can add `self.per_page = 12` to your Model class.
- Navigate to the controller for the model you want to paginate and change the index method like so
```ruby
    def index
        @hikes = Hike.paginate(page: page)
        render json: {
            hikes: @hikes,
            page: @hikes.current_page, 
            pages: @hikes.total_pages
            }
    end
```
- Here I'm using Active Model Serializer, but if you have some other serializer you'll have to modify it a bit. But basically, I'm adding a few extra bits of information about the pages to the JSON data being sent to the front-end. And again, the gem is pretty magic and takes care of managing all of this for you.
- added method for page??
- That's about it for the backend, but in preparation for receiving data from the frontend with the fetch request add the properties `:current_page, :total_pages` to your strong params method.


## Front-end Setup
- First let's get our fetch request set up to send the page number to the backend. Add `/?page=${page_number}` to your URL for the GET request. You'll need to define the page_number constant as either the page passed into the fetch function or default to "1". This is my full fetch request:
```javascript
export const fetchHikes = (page) => {
  const page_number  = page || "1"
  return (dispatch) => {
    dispatch({ type: "LOADING_HIKES" });
    axios
      .get(WEB_URL+`/hikes/?page=${page_number}`, {
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
          Authorization: `Bearer ${localStorage.getItem("token")}`,
        },
      })
      .then((data) => {
        dispatch({ type: "ADD_HIKES", hikes: data.data.hikes });
        dispatch({ type: "SET_PAGES", data: data.data });
      })
      .catch(function (error) {
        NotificationManager.error(
          `Error while fetching hikes!, ${error}`,
          "Error!"
        );
      });
  };
};
```
- You'll notice once I get the data back from the front end I'm calling the action ADD_HIKES - you probably already have this (it's just adding the hikes to the store) but SET_PAGES is probably new to you.
- Pull up your reducer and we'll add the action for SET_PAGES
```javascript
    case "SET_PAGES":
      return {
        ...state,
        page: action.data.page,
        pages: action.data.pages,
        loading: false,
      };
```
and the default state is something like `state = { hikes: [], loading: false, page: 1, pages: 1 }`
- Next is the fun bit, creating the React component for the pagination navigator. But thankfully you'll only need to do this once because you'll write it abstract enough so you can use it for every dataset in each of your apps, right?! Good, let's go.
- Create a React functional component called PaginationComponent, in it, you'll need to `import Pagination from 'react-bootstrap/Pagination'` - check out the [bootstrap react docs](https://react-bootstrap.github.io/components/pagination/) where you can check out the different options available.
- Now we'll go into the container you have where you call your fetch function. Mine is HikesContainer.
- Import the newly created component add it to the render return section. You'll need to pass it the current_page, total_pages, and your fetch function. The page and pages data will be in your store, so you'll need to connect to the store with either the connect and mapStateToProps function or by using the UseSelector Hook and pass those in.
- I've grabbed the pagination navigator code from Boostrap and converted it to use our passed in data
```javascript
import React from 'react'
import Pagination from "react-bootstrap/Pagination";

export default function PaginationComponent(props) {

    let page = props.page
    let pages = props.pages
    let items = [];
    for (let number = page; number <= (page + 4) && number < pages; number++) {
      items.push(
        <Pagination.Item onClick={() => props.fetchData(number)} key={number} active={number === page}>
          {number}
        </Pagination.Item>
      );
    }

    return (
        <div>
          <Pagination>
            <Pagination.First onClick={() => props.fetchData(1)}/>
            <Pagination.Prev onClick={() => props.fetchData(page - 1)}/>
            {items}
            <Pagination.Ellipsis />
            <Pagination.Item onClick={() => props.fetchData(pages)}>{pages}</Pagination.Item>
            <Pagination.Next onClick={() => props.fetchData(page + 1)}/>
            <Pagination.Last onClick={() => props.fetchData(pages)}/>
          </Pagination>
          <br />
        </div>
    )
}
``` 
- You can run your application and this should now work and display 5 page selectors at a time. Each time one is clicked it completed a fetch request to your back end with the page number in the props. The backend returns the data from that page. Easy.
- But, now trying to make it work you pretty quickly find that there are some issues as you shouldn't be able to navigate beyond the total number of pages. So we need to add some logic to disable some buttons. Basically, you want to make sure if you're at the first page you can't go backwards, and when you're at the last page you can't go forwards.
- Add `disabled={page === 1 ? true : false}` to the first and prev.
- Add `disabled={page === pages ? true : false}` to the last and next.

#### Now you should have a working pagination bar, congrats!