Adding Pagination

ExamPro Markdown Lab Part 2
This is part of the ExamPro Next.js course. Additional content will be added to this lab, such as pagination and job type filtering.
In this lab, we will be adding the pagination feature to the existing application
Adding Pagination Feature
http://localhost:3000/jobs would look like this:
Config file
This file will be used to set how many jobs will be displayed on one page
  • Create ./config/index.js
  • export const JOBS_PER_PAGE = 4;
    Pagination Component
  • Create ./components/jobs/Pagination.js
  • import Link from 'next/link';
    
    import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid';
    
    import { JOBS_PER_PAGE } from '@/config/index';
    
    export default function Pagination({ currentPage, numJobs, numPages }) {
      const isFirst = currentPage === 1;
      const isLast = currentPage === numPages;
      const prevPage = `/jobs/page/${currentPage - 1}`;
      const nextPage = `/jobs/page/${currentPage + 1}`;
      const firstJobOfPage = parseInt((currentPage - 1) * JOBS_PER_PAGE + 1);
      const lastJobOfPage = parseInt(currentPage * JOBS_PER_PAGE);
    
      if (numPages === 1) return <></>;
    
      return (
        <div className="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
          <div className="flex-1 flex justify-between sm:hidden">
            {/* If not first page, display the Previous link */}
            {!isFirst && (
              <Link href={prevPage}>
                <a className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
                  Previous
                </a>
              </Link>
            )}
    
            {/* If not last page, display the Next link */}
            {!isLast && (
              <Link href={nextPage}>
                <a className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
                  Next
                </a>
              </Link>
            )}
          </div>
    
          <div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
            <div>
              <p className="text-sm text-gray-700">
                Showing <span className="font-medium">{firstJobOfPage}</span> to{' '}
                <span className="font-medium">{lastJobOfPage > numJobs ? numJobs : lastJobOfPage}</span>{' '}
                of <span className="font-medium">{numJobs}</span> results
              </p>
            </div>
            <div>
              <nav
                className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px"
                aria-label="Pagination"
              >
                {/* If not first page, display the Previous link */}
                {!isFirst && (
                  <Link href={prevPage}>
                    <a className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
                      <span className="sr-only">Previous</span>
                      <ChevronLeftIcon className="h-5 w-5" aria-hidden="true" />
                    </a>
                  </Link>
                )}
    
                {/* Loop through numPages array */}
                {Array.from({ length: numPages }, (_, i) => (
                  <li key={i} className="list-none">
                    <Link href={`/jobs/page/${i + 1}`} passHref>
                      {i == currentPage - 1 ? (
                        <a
                          aria-current="page"
                          className="z-10 bg-orange-50 border-orange-400 text-orange-500 relative inline-flex items-center px-4 py-2 border text-sm font-medium"
                        >
                          {i + 1}
                        </a>
                      ) : (
                        <a className="bg-white border-gray-300 text-gray-500 hover:bg-gray-50 relative inline-flex items-center px-4 py-2 border text-sm font-medium">
                          {i + 1}
                        </a>
                      )}
                    </Link>
                  </li>
                ))}
    
                {/* If not last page, display the Next link */}
                {!isLast && (
                  <Link href={nextPage}>
                    <a className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50">
                      <span className="sr-only">Next</span>
                      <ChevronRightIcon className="h-5 w-5" aria-hidden="true" />
                    </a>
                  </Link>
                )}
              </nav>
            </div>
          </div>
        </div>
      );
    }
    To add pagination, we need a new dynamic route for our job listings.
  • Create pages/jobs/page/[page_index].js file
  • Import fs and path modules
  • Import matter
  • Import Job component
  • Import Layout component
  • Import Pagination
  • import { promises as fs } from 'fs';
    import path from 'path';
    import matter from 'gray-matter';
    
    import { JOBS_PER_PAGE } from '@/config/index';
    
    import Job from '@/components/jobs/Job';
    import Layout from '@/components/Layout';
    import Pagination from '@/components/jobs/Pagination';
  • Create getStaticPaths() function
  • export async function getStaticPaths() {
      // Read from the /jobs directory
      const files = await fs.readdir(path.join('jobs'));
      // Get the number of files and divide by JOBS_PER_PAGE then round up
      const numPages = Math.ceil(files.length / JOBS_PER_PAGE);
    
      let paths = [];
    
      for (let i = 1; i <= numPages; i++) {
        paths.push({
          params: { page_index: i.toString() },
        })
      }
    
      return {
        paths,
        fallback: false,
      }
    }
  • Create getStaticProps() function
  • export async function getStaticProps({ params }) {
      const page = parseInt((params && params.page_index) || 1);
    
      // Read from /jobs directory
      const files = await fs.readdir(path.join('jobs'));
    
      // Map through jobs directory
      const jobs = files.map(async (filename) => {
        // Set 'slug' to name of md file
        const slug = filename.replace('.md', '');
        // Read all markdown from file
        const markdown = await fs.readFile(path.join('jobs', filename), 'utf-8');
        // Extract data from markdown
        const { data } = matter(markdown);
    
        // return slug and data in an array
        return {
          slug,
          data,
        };
      });
    
      // Get total number of jobs
      const numJobs = files.length;
      // Get the number of files and divide by JOBS_PER_PAGE then round up
      const numPages = Math.ceil(files.length / JOBS_PER_PAGE);
      // Get the page index
      const pageIndex = page - 1;
      // Display only the number of jobs based on JOBS_PER_PAGE
      const displayJobs = jobs.slice(pageIndex * JOBS_PER_PAGE, (pageIndex + 1) * JOBS_PER_PAGE);
    
      return {
        props: {
          jobs: await Promise.all(displayJobs),
          numJobs,
          numPages,
          currentPage: page,
        },
      };
    }
  • Create the JobPostings() function
  • export default function JobPostings({ jobs, numJobs, numPages, currentPage }) {
      return (
        <Layout title="Jobs | ExamPro">
          <div className="px-4 py-4 sm:px-6 md:flex md:items-center md:justify-between">
            <div className="flex-1 min-w-0">
              <h2 className="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">
                Job Postings
              </h2>
            </div>
          </div>
          <div className="bg-white my-4 shadow overflow-hidden divide-y divide-gray-200 sm:rounded-md">
            <ul role="list" className="divide-y divide-gray-200">
              {/* Maps through each job */}
              {jobs.map((job, index) => (
                <Job key={index} job={job} />
              ))}
            </ul>
          </div>
          <Pagination currentPage={currentPage} numJobs={numJobs} numPages={numPages} />
        </Layout>
      );
    }
  • Since we most of the same functionality in pages/jobs/index.js and pages/jobs/page/[page_index.js], we can just delete everything in pages/jobs/index.js import like so:
  • import { getStaticProps } from './page/[page_index]';
    import JobPostings from './page/[page_index]';
    
    export { getStaticProps };
    export default JobPostings;

    29

    This website collects cookies to deliver better user experience

    Adding Pagination