Performance
react-data-table-component is heavily memoized. Cell and column components only re-render when their own
props change. Most performance problems we see come from identity churn
in props passed to DataTable, not from the library itself. This page covers
the patterns that matter.
Row count guidance
The table does not virtualize rows. Every row in data renders to the DOM. As
a rough guide:
| Row count | Behavior |
|---|---|
| < 500 | No performance considerations. Render as-is. |
| 500 – 5,000 | Enable pagination with a reasonable paginationPerPage (25–100). Sort and filter stay fast. |
| 5,000 – 50,000 | Use paginationServer and sortServer. Don't pass the full dataset to DataTable. Slice on the server and send only the visible page. |
| > 50,000 | Always server-side. Consider whether a table is the right UI at this scale. Search or visualization tools are usually a better fit. |
The biggest footgun: new references every render
DataTable uses React.memo on its row and cell components.
Memoization is broken by reference equality, not value equality. These patterns silently
re-render every row on every parent render:
// ❌ New array literal every render → all rows re-render
function App({ items }) {
return <DataTable
columns={[
{ name: 'Name', selector: r => r.name }, // new function every render
]}
data={items}
/>;
}
// ❌ Inline conditional row styles → new array → all rows re-render
<DataTable
conditionalRowStyles={[
{ when: r => r.flagged, style: { color: 'red' } },
]}
/* ... */
/> Fix: hoist or memoize the references.
// ✅ Stable column array
const columns: TableColumn<Item>[] = [
{ name: 'Name', selector: r => r.name },
];
function App({ items }) {
return <DataTable columns={columns} data={items} />;
}
// ✅ Or memoize when it depends on state
const columns = useMemo<TableColumn<Item>[]>(
() => [
{ name: 'Name', selector: r => r.name },
{ name: 'Salary', selector: r => r.salary, omit: !showSalary },
],
[showSalary],
); Stabilize event handlers
Pass useCallback-wrapped handlers for any callback prop you read from inside
a row (onRowClicked, onSelectedRowsChange, etc.). Without it,
every render changes the handler identity and the row context updates downstream.
const handleRowClicked = useCallback((row: Employee) => {
navigate(`/employees/${row.id}`);
}, [navigate]);
<DataTable columns={columns} data={data} onRowClicked={handleRowClicked} /> Stabilize the data array
A new data array re-runs sort, pagination slicing, and filter predicates.
If your data comes from a server, store the response in state and only replace it on real
updates. Don't recreate it during render.
// ❌ data is a new array every render
function App() {
const items = useStore(s => s.items.map(transform)); // new array
return <DataTable data={items} columns={columns} />;
}
// ✅ Compute once, memoize
function App() {
const rawItems = useStore(s => s.items);
const items = useMemo(() => rawItems.map(transform), [rawItems]);
return <DataTable data={items} columns={columns} />;
} Sorting is O(n log n) in the table
Client-side sort runs on the full data array on every sort change. For
larger datasets, push sort to the server with sortServer. DataTable will
skip its internal sort and call onSort(column, direction) instead.
<DataTable
columns={columns}
data={pageOfData}
sortServer
onSort={(column, direction) => refetch({ sortBy: column.id, sortDir: direction })}
/> Server-side pagination
When pagination is server-driven, pass only the current page's rows in data
and the total row count in paginationTotalRows. The table treats
data as "this page" and lets the pagination footer drive page changes:
<DataTable
columns={columns}
data={currentPageRows} // just the rows for this page
paginationServer
paginationTotalRows={total} // total across all pages
onChangePage={setPage}
onChangeRowsPerPage={setPerPage}
pagination
/> Column filtering
Built-in filters run client-side on every keystroke. For large datasets, switch to controlled filters and debounce server calls:
import { useState, useEffect } from 'react';
const [filters, setFilters] = useState({});
const [debounced, setDebounced] = useState(filters);
useEffect(() => {
const t = setTimeout(() => setDebounced(filters), 250);
return () => clearTimeout(t);
}, [filters]);
useEffect(() => {
refetch({ filters: debounced });
}, [debounced]);
<DataTable
columns={columns}
data={data}
filterValues={filters}
onFilterChange={(columnId, next) =>
setFilters(prev => ({ ...prev, [columnId]: next }))
}
/> Cell renderers
A custom cell renderer runs once per cell on every row re-render. Keep them
cheap. Don't allocate inside them. Compute style objects outside the renderer if
possible:
// ❌ New style object every cell
{ cell: row => <span style={{ padding: 8, color: row.flagged ? 'red' : 'black' }}>{row.name}</span> }
// ✅ Static + conditional override
const baseStyle = { padding: 8 };
{ cell: row => <span style={row.flagged ? { ...baseStyle, color: 'red' } : baseStyle}>{row.name}</span> } Avoid expensive selectors
selector runs once per cell on render and once per row on every sort
or filter. Keep them O(1):
// ❌ Walks an array every call
{ selector: r => r.tags.find(t => t.primary)?.name ?? '' }
// ✅ Compute upstream, store on the row
const enriched = useMemo(
() => rows.map(r => ({ ...r, primaryTag: r.tags.find(t => t.primary)?.name ?? '' })),
[rows],
);
{ selector: r => r.primaryTag } Animations
animateRows staggers row entrance and animates sort transitions. On very
long pages (hundreds of rows) the cumulative animation cost can be visible. The animation
is automatically disabled when the user has prefers-reduced-motion enabled.
Profiler checklist
If the table feels slow, open React DevTools → Profiler and check:
- Are individual row components re-rendering on parent-state changes that shouldn't affect them?
→ Some prop is changing identity. Check
columns,conditionalRowStyles, callbacks. - Is the body re-rendering on every keystroke in a controlled input outside the table? → Lift the keystroke state higher or memoize the table.
- Is sorting slow on click? → Move to
sortServer. - Is pagination slow on page change? → You're probably recomputing
dataevery render. Memoize it.