Pagination and Infinite Scroll
In this page:
Tracking the Current Page
A simple page state variable (starting at 1) tracks which page of data is currently displayed. Changing this value, usually via Next/Previous buttons, is what triggers showing (or fetching) a different slice of the full dataset.
Note: Keep page state as a plain number — it's easy to reason about and simple to reset.
Warning: Forgetting to reset page back to 1 when a search or filter changes can leave the user looking at 'page 5' of a filtered result that only has 2 pages.
Example: Tracking the Current Page
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const [page, setPage] = React.useState(1);
return (
<div>
<p>Current page: {page}</p>
<button onClick={() => setPage(p => p + 1)}>Next</button>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Slicing Client-Side Data by Page
When all the data is already loaded on the client, pagination is just a matter of slicing the array based on the current page and a fixed page size, using Array.slice() to grab just the relevant chunk to display.
Note: Compute the slice indices as (page - 1) * pageSize and page * pageSize.
Warning: This client-side slicing approach only works when the full dataset is already loaded — for large datasets, server-side pagination (fetching just one page at a time) is usually better.
Example: Slicing Client-Side Data by Page
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const items = Array.from({ length: 23 }, (_, i) => "Item " + (i + 1));
const [page, setPage] = React.useState(1);
const pageSize = 5;
const pageItems = items.slice((page - 1) * pageSize, page * pageSize);
return (
<div>
<ul>{pageItems.map(i => <li key={i}>{i}</li>)}</ul>
<button onClick={() => setPage(p => p + 1)}>Next</button>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Disabling Buttons at the Boundaries
Computing the total number of pages (from the total item count and page size) lets you disable the Previous button on page 1 and the Next button on the last page, preventing users from navigating to pages that don't exist.
Note: Math.ceil(totalItems / pageSize) gives the correct total page count, rounding up for a partial final page.
Warning: Without boundary checks, clicking Next repeatedly past the last page shows an empty page with no items and no clear explanation why.
Example: Disabling Buttons at the Boundaries
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function App() {
const items = Array.from({ length: 12 }, (_, i) => "Item " + (i + 1));
const [page, setPage] = React.useState(1);
const pageSize = 5;
const totalPages = Math.ceil(items.length / pageSize);
return (
<div>
<p>Page {page} of {totalPages}</p>
<button onClick={() => setPage(p => p - 1)} disabled={page === 1}>Prev</button>
<button onClick={() => setPage(p => p + 1)} disabled={page === totalPages}>Next</button>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Fetching all pages upfront instead of only the current one, defeating the purpose of pagination.
- Forgetting to reset the current page back to 1 when a filter or search term changes.
- Not disabling the Next button on the last page, letting users request pages that don't exist.
- Pagination splits a large dataset into pages, fetching or showing only one page's worth at a time.
- A page state variable tracks which page is currently active.
- Next/Previous buttons increment or decrement the page, usually re-triggering a fetch for that page's data.
- Disabling navigation buttons at the first/last page prevents requesting out-of-range pages.
No React-version restriction — built from useState and (optionally) a data-fetching hook.
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: