React: Introduction to Functional Components & Hooks
Tags: react, tutorial
Published: 2020-02-23 08:40:45

When your app needs to present a large list of items, it is more user friendly to provide a pagination mechanism: You show for example only 10 items on one page, and add as many pages as you need to fit all items. Users can browse to any page to see the respective items in this list.

In this article, we will implement a generic, reusable pagination mechanism. We will achieve this by implementing our own custom hook.

## Requirements for Pagination

Pagination divides a list of items into different "pages". It allows users to browse between the pages, either by going to the next, the previous or to jump to any page inside this collection.

From this description, we identify the following requirements:

* R1 should be configurable about the number of items per page
* R2 should be stateful to keep the current page
* R3 should allow to jump to the previous or the next page
* R4 should allow to jump to any page in the collection
* R5 should return the data that corresponds to the current page

## What is a Custom Hook?

In essence, a custom hook is a React functional component which exports props where at least one prop is prefixed with the keyword `use`. To quote the [official React documentation](https://reactjs.org/docs/hooks-custom.html):

> A custom Hook is a JavaScript function whose name starts with "use" and that may call other Hooks.

The prop with the prefix `use` acts like a constructor: It defines the initial value that the functional component needs. Other props can be functions or state values of your hook - you decide what you want to expose.

## Step 1: Configure Number of Items & Current Page

Let’s start the implementation of the custom hook. The R1 requirement is achieved with the  `usePagintation` constructor. It receives the data and the number of items per page. Also, R2 is implemented by having a stateful variable `currentPage`

```jsx
1 import React, {useState} from 'react';
2
3 function usePagination(data, itemsPerPage) {
4   const [currentPage, setCurrentPage] = useState(1);
5   const maxPage = Math.ceil(data.length / itemsPerPage);
6
7   return {currentPage};
8 }
9
10 export default usePagination;
```

In more detail:

* In line 3, we implement the `usePagination` function, passing the parameters `data` and `itemsPerPage`
* In line 3, we define the `currentPage` state variable by using the `useState` built-in hook [^1]
* In line 5, we set the `maxPage` variable, which define the upper bound of the number of pages that we can show.
* In line 10, we export the `usePagination` function.

## Step 2: Increase, Decrease and Go to any Page

These requirements provide the core features of our hook. We will implement them as functions and export them. Thereby, we need to ensure that the value of `currentPage` always lies within the range of 1 and the `maxPage` value.

Let’s code:

```jsx
1  function next() {
2    setCurrentPage((currentPage) => Math.min(currentPage + 1, maxPage));
3  }
4
5  function prev() {
6    setCurrentPage((currentPage) => Math.max(currentPage - 1, 1));
7  }
8
9  function jump(page) {
10   const pageNumber = Math.max(1, page)
11   setCurrentPage((currentPage) => Math.min(pageNumber, maxPage));
12 }
```

* Line 1: The `next` function increases the `currentPage`by 1, but does not exceed `maxPage`
* Line 4: The `prev` function decreases the `currentPage`by 1, but does not go below 1
* Line 7: The `jump` function takes care that `currentPage` stays within both limits

## Step 3: Returning Data of the Current Page

The last step is to implement that only data of the current page is shown.  

```jsx
1 function currentData() {
2   const begin = (currentPage - 1) * itemsPerPage;
3   const end = begin + itemsPerPage;
4   return data.slice(begin, end);
5 }
```

The variable `data` hold all items of the pagination component. From this, we select all exactly `itemsPerPage`, beginning with the value `currentPage - 1` because array indexes start with `0`.

## Pagination Hook: Complete Component

Here is the complete component:

```jsx
1  import React, { useState } from "react";
2
3  function usePagination(data, itemsPerPage) {
4    const [currentPage, setCurrentPage] = useState(1);
5    const maxPage = Math.ceil(data.length / itemsPerPage);
6
7  function currentData() {
8    const begin = (currentPage - 1) * itemsPerPage;
9    const end = begin + itemsPerPage;
10   return data.slice(begin, end);
11  }
12
13  function next() {
14    setCurrentPage((currentPage) => Math.min(currentPage + 1, maxPage));
15  }
16
17  function prev() {
18    setCurrentPage((currentPage) => Math.max(currentPage - 1, 1));
19  }
20
21  function jump(page) {
22    const pageNumber = Math.max(1, page);
23    setCurrentPage((currentPage) => Math.min(pageNumber, maxPage));
24  }
25
26  return { next, prev, jump, currentData, currentPage, maxPage };
27 }
28
29 export default usePagination;
```

## Conclusion

In this article, I showed how to implement a custom react hook. The hook exposes its functions and parts of its state to its caller. The caller invokes functions on the hook, and decides how to render its result and the pagination. Custom hooks are powerful and help you to define reusable functionality.

[^1]: Yes, you can reuse built-in/custom hooks inside other hooks.