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 { 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 QueryClient,15 QueryClientProvider,16 keepPreviousData,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 'table-data',72 columnFilters, //refetch when columnFilters changes73 globalFilter, //refetch when globalFilter changes74 pagination.pageIndex, //refetch when pagination.pageIndex changes75 pagination.pageSize, //refetch when pagination.pageSize changes76 sorting, //refetch when sorting changes77 ],78 queryFn: async () => {79 const fetchURL = new URL(80 '/api/data',81 process.env.NODE_ENV === 'production'82 ? 'https://www.material-react-table.com'83 : 'http://localhost:3000',84 );8586 //read our state and pass it to the API as query params87 fetchURL.searchParams.set(88 'start',89 `${pagination.pageIndex * pagination.pageSize}`,90 );91 fetchURL.searchParams.set('size', `${pagination.pageSize}`);92 fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));93 fetchURL.searchParams.set('globalFilter', globalFilter ?? '');94 fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));9596 //use whatever fetch library you want, fetch, axios, etc97 const response = await fetch(fetchURL.href);98 const json = (await response.json()) as UserApiResponse;99 return json;100 },101 placeholderData: keepPreviousData, //don't go to 0 rows when refetching or paginating to next page102 });103104 //create columns from data105 const columns = useMemo<MRT_ColumnDef<User>[]>(106 () =>107 data.length108 ? Object.keys(data[0]).map((columnId) => ({109 header: columnNames[columnId as keyof User] ?? columnId,110 accessorKey: columnId,111 id: columnId,112 }))113 : [],114 [data],115 );116117 //UPDATE: No longer needed as of v2.10.0118 // useEffect(() => {119 // //if using dynamic columns that are loaded after table instance creation, we will need to set the column order state ourselves120 // setColumnOrder(columns.map((column) => column.id!));121 // }, [columns]);122123 const table = useMaterialReactTable({124 columns,125 data,126 initialState: { showColumnFilters: true },127 manualFiltering: true, //turn off built-in client-side filtering128 manualPagination: true, //turn off built-in client-side pagination129 manualSorting: true, //turn off built-in client-side sorting130 //give loading spinner somewhere to go while loading131 muiTableBodyProps: {132 children: isLoading ? (133 <tr style={{ height: '200px' }}>134 <td />135 </tr>136 ) : undefined,137 },138 muiToolbarAlertBannerProps: isError139 ? {140 color: 'error',141 children: 'Error loading data',142 }143 : undefined,144 onColumnFiltersChange: setColumnFilters,145 // onColumnOrderChange: setColumnOrder,146 onGlobalFilterChange: setGlobalFilter,147 onPaginationChange: setPagination,148 onSortingChange: setSorting,149 renderTopToolbarCustomActions: () => (150 <Tooltip arrow title="Refresh Data">151 <IconButton onClick={() => refetch()}>152 <RefreshIcon />153 </IconButton>154 </Tooltip>155 ),156 rowCount: meta?.totalRowCount ?? 0,157 state: {158 columnFilters,159 // columnOrder,160 globalFilter,161 isLoading,162 pagination,163 showAlertBanner: isError,164 showProgressBars: isRefetching,165 sorting,166 },167 });168169 return <MaterialReactTable table={table} />;170};171172const queryClient = new QueryClient();173174const ExampleWithReactQueryProvider = () => (175 //App.tsx or AppProviders file. Don't just wrap this component with QueryClientProvider! Wrap your whole App!176 <QueryClientProvider client={queryClient}>177 <Example />178 </QueryClientProvider>179);180181export default ExampleWithReactQueryProvider;182
View Extra Storybook Examples