React - Lists, Pagination & Filtering
Overview
Estimated time: 25–35 minutes
Display lists with stable keys, filter them by text, and paginate results on the client.
Try it: Filter + Pagination
View source
const { useMemo, useState } = React;
function Pager({ page, pages, onPage }){
return (
<div style={{marginTop:8}}>
<button onClick={() => onPage(Math.max(1, page-1))} disabled={page===1}>Prev</button>
<span style={{margin:'0 8px'}}>Page {page} / {pages}</span>
<button onClick={() => onPage(Math.min(pages, page+1))} disabled={page===pages}>Next</button>
</div>
);
}
function App(){
const data = useMemo(() => Array.from({length:50}, (_,i) => ({ id:i+1, name:`Item ${i+1}` })), []);
const [q, setQ] = useState('');
const [page, setPage] = useState(1);
const per = 10;
const filtered = data.filter(x => x.name.toLowerCase().includes(q.toLowerCase()));
const pages = Math.max(1, Math.ceil(filtered.length/per));
const start = (page-1)*per;
const slice = filtered.slice(start, start+per);
return (
<div>
<input placeholder="Search" value={q} onChange={e => { setQ(e.target.value); setPage(1); }} />
<ul>{slice.map(x => <li key={x.id}>{x.name}</li>)}</ul>
<Pager page={page} pages={pages} onPage={setPage} />
<div style={{marginTop:8, color:'var(--muted)'}}>Total: {filtered.length}</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('try-list')).render(<App />);
Syntax primer
- Use stable keys (
id
) for list items. - Compute filtered and paginated slices derived from source data.
Common pitfalls
- Using array index as key can cause incorrect item reuse on reordering.
- For large lists, consider virtualization libraries.
Exercises
- Add page size selector (10/25/50).
- Highlight matched search terms.
Checks for Understanding
- Why should list items use stable keys instead of array indexes?
- What derived state is computed from the source list for filtering and pagination?
- Why reset the current page to 1 when the search query changes?
Answers
- Stable keys prevent incorrect DOM reuse when inserting/removing/reordering items.
- Filtered list (by query), total pages from filtered.length, and the current slice via start/length calculations.
- Because the filtered result set may be smaller; staying on a higher page could result in an empty view.