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
After getting into the app folder and installing the dependencies
run the App with
$ npm run dev
As a first step, we will install a third-party library called react-query.
$ npm i react-query
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:
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();
}
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>
);
}
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;
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 };
}
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;
}
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>
);
}
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;
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)