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
- Add input to jump to a page number with validation.
- Disable buttons when the page is at the bounds.
Checks for Understanding
- Why keep the current page in the URL/hash?
- How do you mark the active page button for assistive technologies?
- How do you guard against navigating outside valid page bounds?
Answers
- It preserves state on reload and makes the paginated view shareable via a link.
- Apply aria-current="page" on the active page button/link.
- Clamp the target page between 1 and pages when changing pages.