React - List View with Pagination

Overview

Estimated time: 20–30 minutes

Render a paginated list with accessible controls, preserving the current page in the hash.

Try it: Paginated list

View source
function usePage(total, perPage){
  const [page, setPage] = React.useState(() => Number(new URLSearchParams((location.hash.split('?')[1]||'')).get('page')) || 1);
  React.useEffect(() => {
    const params = new URLSearchParams(location.hash.split('?')[1]||'');
    params.set('page', page);
    location.hash = `#/list?page=${params.get('page')}`;
  }, [page]);
  const pages = Math.ceil(total/perPage);
  const go = p => setPage(Math.max(1, Math.min(pages, p)));
  return {page, setPage:go, pages};
}
function Pager({page, pages, onChange}){
  const ids = Array.from({length:pages}, (_,i)=>i+1);
  return (
    <nav aria-label="Pagination">
      <button onClick={()=> onChange(page-1)} disabled={page===1}>Prev</button>
      {ids.map(p => (
        <button key={p} aria-current={p===page? 'page': undefined} onClick={()=> onChange(p)}>{p}</button>
      ))}
      <button onClick={()=> onChange(page+1)} disabled={page===pages}>Next</button>
    </nav>
  );
}
function App(){
  const items = Array.from({length:100}, (_,i)=> `Item ${i+1}`);
  const perPage = 10;
  const {page, setPage, pages} = usePage(items.length, perPage);
  const start = (page-1)*perPage;
  const slice = items.slice(start, start+perPage);
  return (<div>
    <ul>{slice.map(x => <li key={x}>{x}</li>)}</ul>
    <Pager page={page} pages={pages} onChange={setPage} />
  </div>);
}
ReactDOM.createRoot(document.getElementById('try-list-pag')).render(<App />);

Syntax primer

  • Keep page in URL to preserve state on reload and share links.
  • Use aria-current="page" to mark the active page.

Common pitfalls

  • Resetting to page 1 after navigation—keep it stable across views when appropriate.

Exercises

  1. Add input to jump to a page number with validation.
  2. Disable buttons when the page is at the bounds.

Checks for Understanding

  1. Why keep the current page in the URL/hash?
  2. How do you mark the active page button for assistive technologies?
  3. How do you guard against navigating outside valid page bounds?
Answers
  1. It preserves state on reload and makes the paginated view shareable via a link.
  2. Apply aria-current="page" on the active page button/link.
  3. Clamp the target page between 1 and pages when changing pages.