Scientyfic World

Write Structured content faster — Get your copy of DITA Decoded today. Buy it Now

How to Apply Pagination in Dynamic Table in React JS?

Rendering large datasets directly into tables often leads to cluttered UIs and poor performance. Whether you’re building a user list, an order summary, or an admin dashboard, breaking down the dataset into manageable pages improves both usability and responsiveness. In React, implementing pagination can be done manually using hooks, or with the help of libraries that abstract some of the repetitive logic.

This blog walks through both options—custom pagination logic using useState and useEffect, and a library-driven approach using react-paginate. Each method will help you render dynamic tables cleanly, and adapt easily whether you’re working with local data or integrating a paginated API.

What We’re Solving?

We need to display a long list of data (e.g. users) in a table with a fixed number of rows per page. Using React functional components with hooks, we’ll fetch data from an API and implement pagination on the client side. We show two methods: a custom hook-based pagination (slicing the data array) and using the react-paginate library. We will also outline how to adapt these for server-side pagination (where the API returns only one page at a time).

Approach 1: Custom Pagination using React Hooks

Use useState and useEffect to fetch the full list once and then slice it per page. For example:

function UsersTable() {
  const [users, setUsers] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const itemsPerPage = 5;

  // Fetch data on mount
  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => setUsers(data));
  }, []);

  // Calculate slice indices for current page
  const indexOfLast = currentPage * itemsPerPage;
  const indexOfFirst = indexOfLast - itemsPerPage;
  const currentUsers = users.slice(indexOfFirst, indexOfLast); // only rows for current page:contentReference[oaicite:2]{index=2}

  return (
    <>
      <table>
        <thead>
          <tr><th>ID</th><th>Name</th><th>Email</th></tr>
        </thead>
        <tbody>
          {currentUsers.map(user => (
            <tr key={user.id}>
              <td>{user.id}</td><td>{user.name}</td><td>{user.email}</td>
            </tr>
          ))}
        </tbody>
      </table>
      <div className="pagination">
        {Array.from({ length: Math.ceil(users.length / itemsPerPage) }, (_, i) => (
          <button key={i} onClick={() => setCurrentPage(i + 1)}>
            {i + 1}
          </button>
        ))}
      </div>
    </>
  );
}
JavaScript

This component uses useState for currentPage and a constant itemsPerPage (5) as recommended. We fetch users once with useEffect. We compute indexOfFirst and indexOfLast and use users.slice(indexOfFirst, indexOfLast) to get only the users for the current page. The table’s <thead> is always rendered so the header stays visible. We render a button for each page number by computing Math.ceil(users.length/itemsPerPage) pages. Clicking a page button sets currentPage, which re-slices and shows that page.

Approach 2: Using react-paginate

The react-paginate library provides a pre-built pagination UI with next/prev links and optional page-size dropdown. Install it with npm install react-paginate. In your component, import it and use useState for a zero-based page index. For example:

import ReactPaginate from 'react-paginate';
// ... inside component:
const [page, setPage] = useState(0); // zero-based page index for react-paginate
const itemsPerPage = 5;
const pageCount = Math.ceil(users.length / itemsPerPage);
const currentUsers = users.slice(page * itemsPerPage, (page + 1) * itemsPerPage);

return (
  <>
    <table></table>
    <ReactPaginate
      pageCount={pageCount}
      pageRangeDisplayed={3}
      marginPagesDisplayed={1}
      onPageChange={({ selected }) => setPage(selected)} // event.selected is 0-based:contentReference[oaicite:8]{index=8}
      containerClassName="pagination"
      activeClassName="active"
      previousLabel="Previous"
      nextLabel="Next"
    />
  </>
);
JavaScript

Here pageCount={Math.ceil(users.length/itemsPerPage)} defines how many pages there are. The onPageChange prop is a function that receives an event object; use event.selected to update the page state. We pass CSS class names via containerClassName and activeClassName (e.g. .pagination, .active) to style the controls.

Finally, render the table body using currentUsers as before, and place the <ReactPaginate> bar below it. The CSS classes allow styling of the pagination bar – for example, .active highlights the current page. The output UI will show numbered pages with “Previous/Next” buttons and ellipses if pages are many (as illustrated above).

Adapting for Server-Side Pagination

For large datasets, fetch only one page of data at a time. Modify the above as follows: use a useEffect that depends on [currentPage] and fetches only that page from the API. For example:

useEffect(() => {
  fetch(`/api/users?page=${currentPage}&limit=${itemsPerPage}`)
    .then(res => res.json())
    .then(data => setUsers(data.items)); // data.items is the page content
}, [currentPage]);
JavaScript
  • Replace the client-side .slice() logic: instead of slicing a full array, let the server return exactly the users for the current page.
  • Ensure useEffect watches currentPage so a fetch runs on every page change.
  • In the custom solution, users will now just be one page of rows, and you can compute page numbers if your API returns a total count.
  • In the react-paginate solution, keep the same onPageChange handler but simply update currentPage and set users from the response. Note react-paginate is zero-indexed by default, so if your API expects page=1 for the first page, add 1 to page when building the URL (e.g. page=${page+1}).
  • Use query params like ?page=2&limit=5 as needed.

These adjustments ensure only the needed records are fetched per page. All other UI logic remains the same: the table header is static, and page numbers still use setCurrentPage or setPage via onPageChange to drive the next fetch.

Conclusion

Pagination in dynamic React tables doesn’t require complex dependencies or boilerplate. In this guide, we covered two effective approaches:

  • A custom pagination solution using React hooks (useState, useEffect) to slice data locally and control page navigation.
  • A cleaner UI-driven approach using the react-paginate library for managing pagination logic and rendering controls.

Both methods support a static header and seamless user experience. We also outlined how to adapt each approach for server-side pagination, where only the current page’s data is fetched from the backend using query parameters like ?page=2&limit=5.

No matter which model you choose, the key is separating page state from the full dataset and rendering only what’s needed—either via .slice() for local data or backend filtering for large datasets. This ensures scalability, better performance, and simpler state management.

FAQs:

How do I paginate a table in React without using a library?

You can paginate a table in React using useState to track the current page and .slice() to render only the relevant data per page. Combine it with a custom pagination control (e.g. page number buttons) that updates the current page on click.

What is the difference between client-side and server-side pagination in React?

Client-side pagination loads the entire dataset in the browser and paginates using JavaScript. Server-side pagination fetches only a subset (e.g. limit=5&page=2) from the backend, making it suitable for large datasets or APIs with built-in pagination.

How does react-paginate work with dynamic tables in React?

react-paginate is a UI component that handles page navigation logic. You bind its onPageChange to update your current page state and slice the data accordingly for display. For server-side pagination, trigger an API call in the onPageChange handler.

How do I keep the table header fixed while paginating in React?

Make sure your is outside of the paginated data logic. Only the should be affected by slicing or fetched pages. This way, the table header stays intact across all page changes.

How can I integrate pagination if my backend already supports it?

Skip local slicing. Instead, fetch data directly for the current page using query parameters like ?page=2&limit=5. Update your table content with the new data upon each page change. Both custom pagination and react-paginate support this model with minor adjustments.

Snehasish Konger
Snehasish Konger

Snehasish Konger is a passionate technical writer and the founder of Scientyfic World, a platform dedicated to sharing knowledge on science, technology, and web development. With expertise in React.js, Firebase, and SEO, he creates user-friendly content to help students and tech enthusiasts. Snehasish is also the author of books like DITA Decoded and Mastering the Art of Technical Writing, reflecting his commitment to making complex topics accessible to all.

Articles: 230