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 countBehavior
< 500No performance considerations. Render as-is.
500 – 5,000Enable pagination with a reasonable paginationPerPage (25–100). Sort and filter stay fast.
5,000 – 50,000Use paginationServer and sortServer. Don't pass the full dataset to DataTable. Slice on the server and send only the visible page.
> 50,000Always 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:

  1. Are individual row components re-rendering on parent-state changes that shouldn't affect them? → Some prop is changing identity. Check columns, conditionalRowStyles, callbacks.
  2. Is the body re-rendering on every keystroke in a controlled input outside the table? → Lift the keystroke state higher or memoize the table.
  3. Is sorting slow on click? → Move to sortServer.
  4. Is pagination slow on page change? → You're probably recomputing data every render. Memoize it.