Dynamic Columns (Remote) Example
This example shows how to generate column definitions dynamically from remote data after first render using TanStack Query. You may need to manage the columnOrder
state manually if doing this.
10
1import { lazy, Suspense, 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 // type MRT_ColumnOrderState,10} from 'material-react-table';11import { IconButton, Tooltip } from '@mui/material';12import RefreshIcon from '@mui/icons-material/Refresh';13import {14 keepPreviousData,15 QueryClient,16 QueryClientProvider,17 useQuery,18} from '@tanstack/react-query'; //note: this is TanStack React Query V51920//Your API response shape will probably be different. Knowing a total row count is important though.21type UserApiResponse = {22 data: Array<User>;23 meta: {24 totalRowCount: number;25 };26};2728type User = {29 firstName: string;30 lastName: string;31 address: string;32 state: string;33 phoneNumber: string;34 lastLogin: Date;35};3637const columnNames = {38 firstName: 'First Name',39 lastName: 'Last Name',40 address: 'Address',41 state: 'State',42 phoneNumber: 'Phone Number',43 lastLogin: 'Last Login',44} as const;4546const Example = () => {47 //manage our own state for stuff we want to pass to the API48 const [columnFilters, setColumnFilters] = useState<MRT_ColumnFiltersState>(49 [],50 );51 const [globalFilter, setGlobalFilter] = useState('');52 const [sorting, setSorting] = useState<MRT_SortingState>([]);53 const [pagination, setPagination] = useState<MRT_PaginationState>({54 pageIndex: 0,55 pageSize: 10,56 });5758 //if using dynamic columns that are loaded after table instance creation, we will need to manage the column order state ourselves59 //UPDATE: No longer needed as of v2.10.060 // const [columnOrder, setColumnOrder] = useState<MRT_ColumnOrderState>([]);6162 //consider storing this code in a custom hook (i.e useFetchUsers)63 const {64 data: { data = [], meta } = {}, //your data and api response will probably be different65 isError,66 isRefetching,67 isLoading,68 refetch,69 } = useQuery<UserApiResponse>({70 queryKey: [71 'users-list',72 {73 columnFilters, //refetch when columnFilters changes74 globalFilter, //refetch when globalFilter changes75 pagination, //refetch when pagination changes76 sorting, //refetch when sorting changes77 },78 ],79 queryFn: async () => {80 const fetchURL = new URL('/api/data', location.origin); // nextjs api route8182 //read our state and pass it to the API as query params83 fetchURL.searchParams.set(84 'start',85 `${pagination.pageIndex * pagination.pageSize}`,86 );87 fetchURL.searchParams.set('size', `${pagination.pageSize}`);88 fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));89 fetchURL.searchParams.set('globalFilter', globalFilter ?? '');90 fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));9192 //use whatever fetch library you want, fetch, axios, etc93 const response = await fetch(fetchURL.href);94 const json = (await response.json()) as UserApiResponse;95 return json;96 },97 placeholderData: keepPreviousData, //don't go to 0 rows when refetching or paginating to next page98 });99100 //create columns from data101 const columns = useMemo<MRT_ColumnDef<User>[]>(102 () =>103 data.length104 ? Object.keys(data[0]).map((columnId) => ({105 header: columnNames[columnId as keyof User] ?? columnId,106 accessorKey: columnId,107 id: columnId,108 }))109 : [],110 [data],111 );112113 //UPDATE: No longer needed as of v2.10.0114 // useEffect(() => {115 // //if using dynamic columns that are loaded after table instance creation, we will need to set the column order state ourselves116 // setColumnOrder(columns.map((column) => column.id!));117 // }, [columns]);118119 const table = useMaterialReactTable({120 columns,121 data,122 enableRowSelection: true,123 initialState: { showColumnFilters: true },124 manualFiltering: true, //turn off built-in client-side filtering125 manualPagination: true, //turn off built-in client-side pagination126 manualSorting: true, //turn off built-in client-side sorting127 //give loading spinner somewhere to go while loading128 muiTableBodyProps: {129 children: isLoading ? (130 <tr style={{ height: '200px' }}>131 <td />132 </tr>133 ) : undefined,134 },135 muiToolbarAlertBannerProps: isError136 ? {137 color: 'error',138 children: 'Error loading data',139 }140 : undefined,141 onColumnFiltersChange: setColumnFilters,142 // onColumnOrderChange: setColumnOrder,143 onGlobalFilterChange: setGlobalFilter,144 onPaginationChange: setPagination,145 onSortingChange: setSorting,146 renderTopToolbarCustomActions: () => (147 <Tooltip arrow title="Refresh Data">148 <IconButton onClick={() => refetch()}>149 <RefreshIcon />150 </IconButton>151 </Tooltip>152 ),153 rowCount: meta?.totalRowCount ?? 0,154 state: {155 columnFilters,156 // columnOrder,157 globalFilter,158 isLoading,159 pagination,160 showAlertBanner: isError,161 showProgressBars: isRefetching,162 sorting,163 },164 });165166 return <MaterialReactTable table={table} />;167};168169//react query setup in App.tsx170const ReactQueryDevtoolsProduction = lazy(() =>171 import('@tanstack/react-query-devtools/build/modern/production.js').then(172 (d) => ({173 default: d.ReactQueryDevtools,174 }),175 ),176);177178const queryClient = new QueryClient();179180export default function App() {181 return (182 <QueryClientProvider client={queryClient}>183 <Example />184 <Suspense fallback={null}>185 <ReactQueryDevtoolsProduction />186 </Suspense>187 </QueryClientProvider>188 );189}190
View Extra Storybook Examples