Skip to content

Loading and empty states

A grid is not always full of rows. Three props cover the rest: loading, error and emptyState. The grid picks the right presentation for each from whether it has rows.

Kwame SmithInvitedSales$48.00
Meera NairPausedGrowth$1,073.00
Aarav RossiActiveGrowth$36.00
Meera PrabhakaranActiveResearch$0.00
Meera GarciaChurnedDesign$0.00
Meera DuboisActiveFinance$1,189.00
Mateo IyerChurnedDesign$0.00
Freya KimActiveSupport$609.00
Kwame BoseActiveFinance$0.00
Kwame NguyenChurnedResearch$0.00
0 of 30 row(s) selected.
Page 1 of 3

Wire the three props to your data source. With TanStack Query:

<DataGrid
data={query.data?.rows ?? []}
columns={columns}
loading={query.isFetching}
error={query.error ? query.error.message : null}
onRetry={() => query.refetch()}
height={480}
/>

What shows, in order of precedence:

Condition What the grid shows
error set The error state over everything, rows or not: a warning icon, Something went wrong., your error as the description, and a Retry button when onRetry is given.
loading and no rows A spinner with Loading… below the header; the overlay does not take pointer events.
loading with rows A thin animated bar along the top edge (role="progressbar"). The rows stay in place and usable, which is what you want on a refetch.
No rows, a filter or search active No rows match the current filters. with a Clear button that resets every column filter and the search.
No rows An inbox icon and No records found.

While loading is true the grid also carries aria-busy.

emptyState replaces the default, for both the empty and the filtered-empty case:

<DataGrid
data={customers}
columns={columns}
emptyState={
<Empty className="border-0">
<EmptyHeader>
<EmptyMedia variant="icon"><UsersIcon /></EmptyMedia>
<EmptyTitle>No customers yet</EmptyTitle>
<EmptyDescription>Import a CSV or add one by hand.</EmptyDescription>
</EmptyHeader>
<Button size="sm">Add customer</Button>
</Empty>
}
/>

It renders inside the grid, so a component you pass can call useDataGridContext() to know whether filters are active and offer its own Clear (table.resetColumnFilters(); table.resetGlobalFilter(true)).

  • The overlay sits below the header. The header row stays visible and usable in every state, so a filter that emptied the grid can be cleared from the header that set it.
  • Give it room. The states centre themselves in the scroll area. With no height an empty or loading grid grows to a minimum of 12rem so the message fits under the header; set height or fillHeight for a steadier frame, as the demo does.
  • Refetch, don’t flash. Keep the previous rows while the next page loads (placeholderData: keepPreviousData in TanStack Query) and loading={isFetching} shows the bar rather than swapping the rows for a spinner. The server-side example does exactly this.
  • Error wins. An error is shown even when rows are present, over them. Clear error when the next request succeeds.
  • Strings. loading, empty, emptyFiltered, error, retry and clearFilters in labels change the wording; see Internationalisation.
Option Type Default What it does
loading boolean false The bar with rows, the spinner without.
error React.ReactNode null Any node; a string is the common case. Truthy shows the error state.
onRetry () => void Adds the Retry button to the error state.
emptyState React.ReactNode Replaces both empty states.
height / fillHeight Not state props, but the states need the room.

The same four props exist on DataGridRoot for composed layouts.