React - Collection Grid with Pagination

Overview

Estimated time: 20–30 minutes

Render a card grid with fixed-height thumbnails and responsive wrapping, plus pagination controls.

Try it: Card grid

View source
function Grid({items}){
  return (
    <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(140px, 1fr))', gap:12}}>
      {items.map((it) => (
        <article key={it.id} style={{border:'1px solid var(--border)', borderRadius:8, overflow:'hidden'}}>
          <div style={{height:100, background:'#eee'}}><img alt="" loading="lazy" src={`https://picsum.photos/seed/${it.id}/300/200`} style={{width:'100%', height:'100%', objectFit:'cover'}} /></div>
          <div style={{padding:8}}>
            <h4 style={{margin:'4px 0'}}>{it.title}</h4>
            <p style={{margin:0, color:'#666'}}>{it.subtitle}</p>
          </div>
        </article>
      ))}
    </div>
  );
}
function Pager({page, pages, onChange}){
  return (<div style={{display:'flex', gap:8, justifyContent:'center', marginTop:8}}>
    <button onClick={()=> onChange(page-1)} disabled={page===1}>Prev</button>
    <span>Page {page} / {pages}</span>
    <button onClick={()=> onChange(page+1)} disabled={page===pages}>Next</button>
  </div>);
}
function App(){
  const all = Array.from({length:48}, (_,i)=> ({id:i+1, title:`Card ${i+1}`, subtitle:'Example subtitle'}));
  const perPage = 12; const [page, setPage] = React.useState(1);
  const pages = Math.ceil(all.length/perPage); const start=(page-1)*perPage;
  const items = all.slice(start, start+perPage);
  return (<div>
    <Grid items={items} />
    <Pager page={page} pages={pages} onChange={setPage} />
  </div>);
}
ReactDOM.createRoot(document.getElementById('try-grid-pag')).render(<App />);

Syntax primer

  • Use CSS grid with auto-fill and minmax for responsive wrapping.
  • Keep thumbnails same height via a fixed container and object-fit: cover.

Common pitfalls

  • Layout shift due to images loading—reserve space with fixed height.

Exercises

  1. Add a selectable card state and show selected count.
  2. Persist the current page in the hash or query string.

Checks for Understanding

  1. Why use object-fit: cover inside a fixed-height container for thumbnails?
  2. How do you compute which items to render for the current page?
  3. What are the tradeoffs of client-side pagination on large datasets?
Answers
  1. It preserves aspect ratio while filling the box, preventing layout shift from varying image sizes.
  2. Calculate pages = ceil(total/perPage), start = (page-1)*perPage, then slice(all, start, start+perPage).
  3. It’s simple and fast for small lists but requires loading all data; for large datasets use server-side pagination or virtualization.