Async Loading UI Feature Guide
While you are fetching your data, you may want to show some loading indicators. Material React Table has some nice loading UI features built in that look better than a simple spinner.
This guide is mostly focused on the loading UI features. Make sure to also check out the Remote Data and React Query examples for server-side logic examples.
Relevant Table Options
# | Prop Name | Type | Default Value | More Info Links | |
---|---|---|---|---|---|
1 |
| Material UI CircularProgress Props | |||
2 |
| Material UI LinearProgress Props | |||
3 |
| Material UI Skeleton Props | |||
Relevant State Options
isLoading UI
There are three different loading UI features that are built into Material React Table:
Loading Overlay - shows spinner overlay over the table container.
Cell Skeletons - show pretty and shimmering skeletons for each cell.
Linear Progress Bars - shows progress bars above and/or below the table.
You can use any combination of these loading UIs by managing the showLoadingOverlay
, showSkeletons
, and showProgressBars
states.
There are also two other loading states that are shortcuts for combining some of the above states:
isLoading
- shows loading overlay and cell skeletons.isSaving
- shows the progress bars and adds spinners to the save buttons in editing features.
Here is some of the recommended loading UI that you might use with React Query:
const {data = [],isLoading: isLoadingTodos,isRefetching: isRefetchingTodos,} = useQuery({/**/});const { mutate, isPending: isSavingTodos } = useMutation({/**/});const table = useMaterialReactTable({columns,data,state: {isLoading: isLoadingTodos, //cell skeletons and loading overlayshowProgressBars: isRefetchingTodos, //progress bars while refetchingisSaving: isSavingTodos, //progress bars and save button spinners},});return <MaterialReactTable table={table} />;
Note: The Loading Overlay UI makes the table container non-interactive while it is showing. This is usually desired while no data is yet in the table. Consider avoiding using the Loading Overlay UI during "refetching" operations like filtering, sorting, or pagination.
Customize Loading UI
You can customize the loading UI by passing props to the muiSkeletonProps
, muiLinearProgressProps
, and muiCircularProgressProps
props.
const table = useMaterialReactTable({columns,data,muiSkeletonProps: {animation: 'wave',},muiLinearProgressProps: {color: 'secondary',},muiCircularProgressProps: {color: 'secondary',},});return <MaterialReactTable table={table} />;
First Name | Last Name | Email | City |
---|---|---|---|
1import { useMemo } from 'react';2import { MaterialReactTable, type MRT_ColumnDef } from 'material-react-table';3import { type Person } from './makeData';45const data: Array<Person> = [];67const Example = () => {8 const columns = useMemo<MRT_ColumnDef<Person>[]>(9 //column definitions...30 );3132 return (33 <MaterialReactTable34 columns={columns}35 data={data}36 state={{ isLoading: true }}37 muiCircularProgressProps={{38 color: 'secondary',39 thickness: 5,40 size: 55,41 }}42 muiSkeletonProps={{43 animation: 'pulse',44 height: 28,45 }}46 />47 );48};4950export default Example;51
Custom Loading Spinner component
New in v2.11.0
If you need to use a custom loading spinner component other than the built-in MUI one, you can now pass in custom component in the muiCircularProgressProps
Component
prop.
import { MyCustomSpinner } from './MyCustomSpinner';const table = useMaterialReactTable({columns,data,muiCircularProgressProps: {Component: <MyCustomSpinner />,},});
Only Show Progress Bars or Skeletons
If you do not want both progress bars and cell skeletons to show, you can use the showProgressBars
and showSkeletons
states, instead.
const table = useMaterialReactTable({columns,data,state: {showProgressBars: true, //or showSkeletons},});
First Name | Last Name | Email | City |
---|---|---|---|
Dylan | Murray | dmurray@yopmail.com | East Daphne |
Raquel | Kohler | rkholer33@yopmail.com | Columbus |
Ervin | Reinger | ereinger@mailinator.com | South Linda |
Brittany | McCullough | bmccullough44@mailinator.com | Lincoln |
Branson | Frami | bframi@yopmain.com | New York |
Kevin | Klein | kklien@mailinator.com | Nebraska |
1import { useEffect, useMemo, useState } from 'react';2import { MaterialReactTable, type MRT_ColumnDef } from 'material-react-table';3import { data, type Person } from './makeData';4import { Button } from '@mui/material';56const Example = () => {7 const columns = useMemo<MRT_ColumnDef<Person>[]>(8 //column definitions...29 );3031 const [progress, setProgress] = useState(0);3233 //simulate random progress for demo purposes34 useEffect(() => {35 const interval = setInterval(() => {36 setProgress((oldProgress) => {37 const newProgress = Math.random() * 20;38 return Math.min(oldProgress + newProgress, 100);39 });40 }, 1000);41 return () => clearInterval(interval);42 }, []);4344 return (45 <MaterialReactTable46 columns={columns}47 data={data}48 muiLinearProgressProps={({ isTopToolbar }) => ({49 color: 'secondary',50 variant: 'determinate', //if you want to show exact progress value51 value: progress, //value between 0 and 10052 sx: {53 display: isTopToolbar ? 'block' : 'none', //hide bottom progress bar54 },55 })}56 renderTopToolbarCustomActions={() => (57 <Button onClick={() => setProgress(0)} variant="contained">58 Reset59 </Button>60 )}61 state={{ showProgressBars: true }}62 />63 );64};6566export default Example;67
Full Loading and Server-Side Logic Example
Here is a copy of the full React Query example.
First Name | Last Name | Address | State | Phone Number | Last Login |
---|---|---|---|---|---|
1import { useMemo, useState } from 'react';2import {3 MaterialReactTable,4 useMaterialReactTable,5 type MRT_ColumnDef,6 type MRT_ColumnFiltersState,7 type MRT_PaginationState,8 type MRT_SortingState,9} from 'material-react-table';10import { IconButton, Tooltip } from '@mui/material';11import RefreshIcon from '@mui/icons-material/Refresh';12import {13 QueryClient,14 QueryClientProvider,15 keepPreviousData,16 useQuery,17} from '@tanstack/react-query'; //note: this is TanStack React Query V51819//Your API response shape will probably be different. Knowing a total row count is important though.20type UserApiResponse = {21 data: Array<User>;22 meta: {23 totalRowCount: number;24 };25};2627type User = {28 firstName: string;29 lastName: string;30 address: string;31 state: string;32 phoneNumber: string;33 lastLogin: Date;34};3536const Example = () => {37 //manage our own state for stuff we want to pass to the API38 const [columnFilters, setColumnFilters] = useState<MRT_ColumnFiltersState>(39 [],40 );41 const [globalFilter, setGlobalFilter] = useState('');42 const [sorting, setSorting] = useState<MRT_SortingState>([]);43 const [pagination, setPagination] = useState<MRT_PaginationState>({44 pageIndex: 0,45 pageSize: 10,46 });4748 //consider storing this code in a custom hook (i.e useFetchUsers)49 const {50 data: { data = [], meta } = {}, //your data and api response will probably be different51 isError,52 isRefetching,53 isLoading,54 refetch,55 } = useQuery<UserApiResponse>({56 queryKey: [57 'table-data',58 columnFilters, //refetch when columnFilters changes59 globalFilter, //refetch when globalFilter changes60 pagination.pageIndex, //refetch when pagination.pageIndex changes61 pagination.pageSize, //refetch when pagination.pageSize changes62 sorting, //refetch when sorting changes63 ],64 queryFn: async () => {65 const fetchURL = new URL('/api/data', location.origin);6667 //read our state and pass it to the API as query params68 fetchURL.searchParams.set(69 'start',70 `${pagination.pageIndex * pagination.pageSize}`,71 );72 fetchURL.searchParams.set('size', `${pagination.pageSize}`);73 fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));74 fetchURL.searchParams.set('globalFilter', globalFilter ?? '');75 fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));7677 //use whatever fetch library you want, fetch, axios, etc78 const response = await fetch(fetchURL.href);79 const json = (await response.json()) as UserApiResponse;80 return json;81 },82 placeholderData: keepPreviousData, //don't go to 0 rows when refetching or paginating to next page83 });8485 const columns = useMemo<MRT_ColumnDef<User>[]>(86 //column definitions...120 );121122 const table = useMaterialReactTable({123 columns,124 data,125 initialState: { showColumnFilters: true },126 manualFiltering: true, //turn off built-in client-side filtering127 manualPagination: true, //turn off built-in client-side pagination128 manualSorting: true, //turn off built-in client-side sorting129 muiToolbarAlertBannerProps: isError130 ? {131 color: 'error',132 children: 'Error loading data',133 }134 : undefined,135 onColumnFiltersChange: setColumnFilters,136 onGlobalFilterChange: setGlobalFilter,137 onPaginationChange: setPagination,138 onSortingChange: setSorting,139 renderTopToolbarCustomActions: () => (140 <Tooltip arrow title="Refresh Data">141 <IconButton onClick={() => refetch()}>142 <RefreshIcon />143 </IconButton>144 </Tooltip>145 ),146 rowCount: meta?.totalRowCount ?? 0,147 state: {148 columnFilters,149 globalFilter,150 isLoading,151 pagination,152 showAlertBanner: isError,153 showProgressBars: isRefetching,154 sorting,155 },156 });157158 return <MaterialReactTable table={table} />;159};160161const queryClient = new QueryClient();162163const ExampleWithReactQueryProvider = () => (164 //App.tsx or AppProviders file. Don't just wrap this component with QueryClientProvider! Wrap your whole App!165 <QueryClientProvider client={queryClient}>166 <Example />167 </QueryClientProvider>168);169170export default ExampleWithReactQueryProvider;171