DEV Community

Cover image for DIY usePagination - React with Typescript
Matan Shaviro
Matan Shaviro

Posted on • Updated on

DIY usePagination - React with Typescript

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 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

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

Image description

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

Image description 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

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.

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();
}
Enter fullscreen mode Exit fullscreen mode

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 :

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

App.tsx :

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;
Enter fullscreen mode Exit fullscreen mode

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 :

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 };
}
Enter fullscreen mode Exit fullscreen mode

In order to display all the items we want on each page, we will define two local states:

  • currentPage: saves the current page the user is on
  • itemsPerPage: 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 :

.pagination {
  display: flex;
  justify-content: center;
  gap: 10px;
}

.page {
  font-weight: bold;
  color: darkblue;
  cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

components/Pagination.tsx :

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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 :

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;
Enter fullscreen mode Exit fullscreen mode

Our Final App - Source Code

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.

Top comments (0)