DIY usePagination - React with Typescript
Published: 2023-01-05 06:22:55

Many applications may need to use multiple pages for easier navigation for better user experience. A good example would be Blog app, An application that displays a list of blog posts and allows users to navigate through them by dividing them into pages.

I will demonstrate how to create a custom hook that implements the logic that pagination brings by displaying a list of blog posts fetched via external API - **https://jsonplaceholder.typicode.com/**.
The complete code is available at the [use-pagination-diy](https://github.com/matan3sh/blog-posts/tree/main/diy-usepagination) GitHub repository.

### Setting up the project

Let's start by creating a new **Vite** project of react with typescript.
`$ npm create vite@latest`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5171tbtans46cj1smvkw.png)

After getting into the app folder and installing the dependencies
run the App with
`$ npm run dev`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yp396j36199m1j1jedfb.png)

As a first step, we will install a third-party library called react-query.
`$ npm i react-query`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h1wckqnrgjak8wpvlx34.png) With this library, we will easily be able to fetch data from the API.

Inside the src folder we will create three additional folders:
- api
- components
- hooks

And delete the **`App.css`** file to read from one style file.
Our app file structure should look like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g7viwjj5as5r4k5snhr5.png)

### Fetching Data
We will create a new file in the API folder called **`post.ts`**. In this file, we will write the function to import posts from the external API. We will then transfer them to the component.
```javascript
export type Post = {
  userId: number;
  id: number;
  title: string;
  body: string;
};

export async function getPosts() {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  return res.json();
}
```

### Displaying the Data
By using react-query, we'll fetch the data into the **App.tsx** file, then create a new component that will be inside the components folder and will be responsible for displaying a list of posts. 

**`components/Posts.tsx`** :
```javascript
import { Post } from "../api/post";

interface Props {
  posts: Post[];
}

export function Posts({ posts }: Props) {
  return (
    <div>
      {posts.map((post) => (
        <p key={post.id}>{post.title}</p>
      ))}
    </div>
  );
}
```

**`App.tsx`** :
```javascript
import { useQuery } from "react-query";
import { getPosts, Post } from "./api/post";

import { Posts } from "./components/Posts";

function App() {
  const {
    data: posts,
    isLoading,
    isError,
    error,
  } = useQuery<Post[], Error>("posts", getPosts);

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (isError || !posts) {
    return <div>{error?.message}</div>;
  }

  return (
    <div className="App">
      <Posts posts={posts} />
    </div>
  );
}

export default App;
```

### Implement usePagination Hook
Our next step will be to create the hook that will be responsible for pagination, revealing the necessary data to the Posts component so that it can present the desired page to the user.

**`hooks/usePagination.ts`** :
```javascript
import { useState } from "react";

const initialData = {
  currentPage: 1,
  itemsPerPage: 10,
};

export function usePagination<T>(items: T) {
  const [currentPage, setCurrentPage] = useState(initialData.currentPage);
  const [itemsPerPage] = useState(initialData.itemsPerPage);

  if (!Array.isArray(items)) throw "Expected items to be an array";

  const indexOfLast = currentPage * itemsPerPage;
  const indexOfFirst = indexOfLast - itemsPerPage;

  const currentItems = items.slice(indexOfFirst, indexOfLast);
  const paginate = (pageNumber: number) => setCurrentPage(pageNumber);

  return { currentItems, itemsPerPage, paginate };
}
```
In order to display all the items we want on each page, we will define two local states: 
- itemsPerPage: saves the current page the user is on
- currentPage: saves the amount of items we want to be displayed while the user is on that page

By looking through rows 14+15 of the two states we defined, we will find the first and last indexes of the list.

### Create Pagination Component
During this step, we will prepare the component that will show us the navigation between the pages. First, we will add two new classes to our **`index.css`** file in order to display the navigation bar between the pages (the pagination) in a normal manner.

**Two new classes at `index.css` file** :
```css
.pagination {
  display: flex;
  justify-content: center;
  gap: 10px;
}

.page {
  font-weight: bold;
  color: darkblue;
  cursor: pointer;
}
```

**`components/Pagination.tsx`** :
```javascript
interface Props {
  postsPerPage: number;
  totalPosts: number;
  paginate: (value: number) => void;
}

export function Pagination({ postsPerPage, totalPosts, paginate }: Props) {
  const pageNumbers = Array(Math.ceil(totalPosts / postsPerPage))
    .fill(0)
    .map((e, i) => i + 1);

  return (
    <ul className="pagination">
      {pageNumbers.map((pageNumber) => (
        <li key={pageNumber} onClick={() => paginate(pageNumber)}>
          <span className="page">{pageNumber}</span>
        </li>
      ))}
    </ul>
  );
}
```

###Wrapping up
As a result of the work we have accomplished so far, we will connect it all to one **`App.tsx`** the component that will display the list and paginate it.

**`App.tsx`** :
```javascript
import { useQuery } from "react-query";
import { getPosts, Post } from "./api/post";
import { usePagination } from "./hooks/usePagination";

import { Posts } from "./components/Posts";
import { Pagination } from "./components/Pagination";

function App() {
  const {
    data: posts,
    isLoading,
    isError,
    error,
  } = useQuery<Post[], Error>("posts", getPosts);
  const { currentItems, itemsPerPage, paginate } = usePagination<Post[]>(
    posts ?? []
  );

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (isError || !posts) {
    return <div>{error?.message}</div>;
  }

  return (
    <div className="App">
      <Posts posts={currentItems} />
      <Pagination
        postsPerPage={itemsPerPage}
        totalPosts={posts.length}
        paginate={paginate}
      />
    </div>
  );
}

export default App;
```

### Our Final App - [Source Code](https://github.com/matan3sh/blog-posts/tree/main/diy-usepagination)


This example shows how we set up a pagination hook by sending the current page, items per page, and paginate function to the component.

Our application was developed in React, but you can use any other framework to apply the same logic. Hopefully, you learned something new today. 

Feel free to leave a comment if you have any questions.