Search code examples
jqueryreactjsangularjsnext.js

Pagination Navigation Not Displaying All Pages in React Component


I am working on a React component that fetches NFT data and displays it in a paginated table. However, I'm having trouble with the pagination navigation. Specifically, I want to show all available pages as clickable numbers (e.g., 1, 2, 3, 4, ..., 147) so that users can navigate to any specific page directly. Currently, the pagination is not working as expected, and I need help to display all page numbers correctly.

"use client";
import { useEffect, useState } from 'react';
import Link from 'next/link';
import styles from '../page.module.css';
import Form from 'react-bootstrap/Form';

const NftData = () => {
  const [cryptoData, setCryptoData] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [itemsPerPage, setItemsPerPage] = useState(20);

  useEffect(() => {
    fetch(`https://apicall.trabic.com/api/v1/nft/getNFT?page=${currentPage}&itemsPerPage=${itemsPerPage}`)
      .then((response) => response.json())
      .then((data) => setCryptoData(data))
      .catch((error) => console.error('Error fetching data:', error));
  }, [currentPage, itemsPerPage]);

  const totalPages = Math.ceil(cryptoData.length / itemsPerPage);

  const handlePageChange = (page: number) => {
    if (page > 0 && page <= totalPages) {
      setCurrentPage(page);
    }
  };

  return (
    <div className='container'>
      <table className={`${styles.tableStl} table`}>
        <thead>
          <tr>
            <th scope="col">#</th>
            <th scope="col">NFT</th>
            <th scope="col">Floor Price</th>
            <th scope="col">24h Change</th>
            <th scope="col">24h Volume</th>
            <th scope="col">Owners</th>
            <th scope="col">Assets Change</th>
            <th scope="col">Timestamp</th>
          </tr>
        </thead>
        <tbody>
          {cryptoData.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage).map((crypto: any) => (
            <tr key={crypto.index}>
              <th scope="row">{crypto.index}</th>
              <td>{crypto.nft}</td>
              <td>{crypto.floor_price}</td>
              <td className={crypto.total_24h.startsWith('-') ? 'text-danger' : 'text-success'}>
                {crypto.total_24h}
              </td>
              <td>{crypto.total_24h_volume}</td>
              <td>{crypto.owners}</td>
              <td className={crypto.total_assets.startsWith('-') ? 'text-danger' : 'text-success'}>
                {crypto.total_assets}
              </td>
              <td>{new Date(crypto.time_stamp).toLocaleString()}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <div className='container'>
        <div className='row'>
          <div className='col-md-3 col-12'>
            <p className='float-start fs-6 textPara'>
              Showing {(currentPage - 1) * itemsPerPage + 1} to {Math.min(currentPage * itemsPerPage, cryptoData.length)} of {cryptoData.length} results
            </p>
          </div>
          <div className='col-md-6 col-12'>
            <nav aria-label="Page navigation example my-3">
              <ul className="pagination d-flex justify-content-center">
                <li className="page-item">
                  <Link href="#" aria-label="Previous" onClick={() => handlePageChange(currentPage - 1)}>
                    <span aria-hidden="true">&laquo;</span>
                  </Link>
                </li>
                {Array.from({ length: totalPages }, (_, index) => index + 1).map(pageNumber => (
                  <li key={pageNumber} className="page-item">
                    <Link
                      href="#"
                      className={`page-link ${pageNumber === currentPage ? 'active' : ''}`}
                      onClick={() => handlePageChange(pageNumber)}
                    >
                      {pageNumber}
                    </Link>
                  </li>
                ))}
                <li className="page-item">
                  <Link href="#" aria-label="Next" onClick={() => handlePageChange(currentPage + 1)}>
                    <span aria-hidden="true">&raquo;</span>
                  </Link>
                </li>
              </ul>
            </nav>
          </div>
          <div className='col-md-3 col-12'>
            <Form.Select size="lg" className='float-end w-25' onChange={(e) => {
              setItemsPerPage(parseInt(e.target.value));
              setCurrentPage(1); // Reset current page to 1 when items per page changes
            }}>
              <option value="20">20</option>
              <option value="50">50</option>
              <option value="100">100</option>
              <option value="300">300</option>
            </Form.Select>
          </div>
        </div>
      </div>
    </div>
  );
};

export default NftData;

Solution

  • First you need to create an API, or in the existing API return the number of pages, based on the pagination size.

    I can see the server is already doing pagination.

    https://abc.abc.com/api/v1/nft/getNFT?page=2&itemsPerPage=5
    

    For you so you need not slice the array, just display it directly.

        <tbody>
          {cryptoData
            .map((crypto: any) => (
              <tr key={crypto.index}>
    

    Secondly, since the total pages will always be 1, since the API is paginating the results. You need to fetch the total pages based on the pagination size, using another API instead.

    I set the totalPages variable to 20, then it started working.

    'use client';
    import { useEffect, useState } from 'react';
    import Link from 'next/link';
    import Form from 'react-bootstrap/Form';
    
    const NftData = () => {
      const [cryptoData, setCryptoData] = useState([]);
      const [currentPage, setCurrentPage] = useState(1);
      const [itemsPerPage, setItemsPerPage] = useState(5);
      const [totalPages, setTotalPages] = useState(20);
    
      useEffect(() => {
        fetch(
          `https://apicall.trabic.com/api/v1/nft/getNFT?page=${currentPage}&itemsPerPage=${itemsPerPage}`
        )
          .then((response) => response.json())
          .then((data) => setCryptoData(data))
          .catch((error) => console.error('Error fetching data:', error));
      }, [currentPage, itemsPerPage]);
    
      // useEffect(() => {
      //   console.log('cal', cryptoData.length);
      //   setTotalPages(Math.ceil(cryptoData.length / itemsPerPage));
      // }, [cryptoData]);
    
      const handlePageChange = (page: number) => {
        if (page > 0 && page <= totalPages) {
          setCurrentPage(page);
        }
      };
    
      return (
        <div className="container">
          <table className={` table`}>
            <thead>
              <tr>
                <th scope="col">#</th>
                <th scope="col">NFT</th>
                <th scope="col">Floor Price</th>
                <th scope="col">24h Change</th>
                <th scope="col">24h Volume</th>
                <th scope="col">Owners</th>
                <th scope="col">Assets Change</th>
                <th scope="col">Timestamp</th>
              </tr>
            </thead>
            <tbody>
              {cryptoData.map((crypto: any) => (
                <tr key={crypto.index}>
                  <th scope="row">{crypto.index}</th>
                  <td>{crypto.nft}</td>
                  <td>{crypto.floor_price}</td>
                  <td
                    className={
                      crypto.total_24h.startsWith('-')
                        ? 'text-danger'
                        : 'text-success'
                    }
                  >
                    {crypto.total_24h}
                  </td>
                  <td>{crypto.total_24h_volume}</td>
                  <td>{crypto.owners}</td>
                  <td
                    className={
                      crypto.total_assets.startsWith('-')
                        ? 'text-danger'
                        : 'text-success'
                    }
                  >
                    {crypto.total_assets}
                  </td>
                  <td>{new Date(crypto.time_stamp).toLocaleString()}</td>
                </tr>
              ))}
            </tbody>
          </table>
    
          <div className="container">
            <div className="row">
              <div className="col-md-3 col-12">
                <p className="float-start fs-6 textPara">
                  Showing {(currentPage - 1) * itemsPerPage + 1} to{' '}
                  {Math.min(currentPage * itemsPerPage, cryptoData.length)} of{' '}
                  {cryptoData.length} results
                </p>
              </div>
              <div className="col-md-6 col-12">
                <nav aria-label="Page navigation example my-3">
                  <ul className="pagination d-flex justify-content-center">
                    <li className="page-item">
                      <Link
                        href="#"
                        aria-label="Previous"
                        onClick={() => handlePageChange(currentPage - 1)}
                      >
                        <span aria-hidden="true">&laquo;</span>
                      </Link>
                    </li>
                    {Array.from(
                      { length: totalPages },
                      (_, index) => index + 1
                    ).map((pageNumber) => (
                      <li key={pageNumber} className="page-item">
                        <Link
                          href="#"
                          className={`page-link ${
                            pageNumber === currentPage ? 'active' : ''
                          }`}
                          onClick={() => handlePageChange(pageNumber)}
                        >
                          {pageNumber}
                        </Link>
                      </li>
                    ))}
                    <li className="page-item">
                      <Link
                        href="#"
                        aria-label="Next"
                        onClick={() => handlePageChange(currentPage + 1)}
                      >
                        <span aria-hidden="true">&raquo;</span>
                      </Link>
                    </li>
                  </ul>
                </nav>
              </div>
              <div className="col-md-3 col-12">
                <Form.Select
                  size="lg"
                  className="float-end w-25"
                  onChange={(e) => {
                    setItemsPerPage(parseInt(e.target.value));
                    setCurrentPage(1); // Reset current page to 1 when items per page changes
                  }}
                >
                  <option value="20">20</option>
                  <option value="50">50</option>
                  <option value="100">100</option>
                  <option value="300">300</option>
                </Form.Select>
              </div>
            </div>
          </div>
        </div>
      );
    };
    
    export default NftData;
    

    Stackblitz Demo