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('/api/data', location.origin);8081 //read our state and pass it to the API as query params82 fetchURL.searchParams.set(83 'start',84 `${pagination.pageIndex * pagination.pageSize}`,85 );86 fetchURL.searchParams.set('size', `${pagination.pageSize}`);87 fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));88 fetchURL.searchParams.set('globalFilter', globalFilter ?? '');89 fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));9091 //use whatever fetch library you want, fetch, axios, etc92 const response = await fetch(fetchURL.href);93 const json = (await response.json()) as UserApiResponse;94 return json;95 },96 placeholderData: keepPreviousData, //don't go to 0 rows when refetching or paginating to next page97 });9899 //create columns from data100 const columns = useMemo<MRT_ColumnDef<User>[]>(101 () =>102 data.length103 ? Object.keys(data[0]).map((columnId) => ({104 header: columnNames[columnId as keyof User] ?? columnId,105 accessorKey: columnId,106 id: columnId,107 }))108 : [],109 [data],110 );111112 //UPDATE: No longer needed as of v2.10.0113 // useEffect(() => {114 // //if using dynamic columns that are loaded after table instance creation, we will need to set the column order state ourselves115 // setColumnOrder(columns.map((column) => column.id!));116 // }, [columns]);117118 const table = useMaterialReactTable({119 columns,120 data,121 enableRowSelection: true,122 initialState: { showColumnFilters: true },123 manualFiltering: true, //turn off built-in client-side filtering124 manualPagination: true, //turn off built-in client-side pagination125 manualSorting: true, //turn off built-in client-side sorting126 //give loading spinner somewhere to go while loading127 muiTableBodyProps: {128 children: isLoading ? (129 <tr style={{ height: '200px' }}>130 <td />131 </tr>132 ) : undefined,133 },134 muiToolbarAlertBannerProps: isError135 ? {136 color: 'error',137 children: 'Error loading data',138 }139 : undefined,140 onColumnFiltersChange: setColumnFilters,141 // onColumnOrderChange: setColumnOrder,142 onGlobalFilterChange: setGlobalFilter,143 onPaginationChange: setPagination,144 onSortingChange: setSorting,145 renderTopToolbarCustomActions: () => (146 <Tooltip arrow title="Refresh Data">147 <IconButton onClick={() => refetch()}>148 <RefreshIcon />149 </IconButton>150 </Tooltip>151 ),152 rowCount: meta?.totalRowCount ?? 0,153 state: {154 columnFilters,155 // columnOrder,156 globalFilter,157 isLoading,158 pagination,159 showAlertBanner: isError,160 showProgressBars: isRefetching,161 sorting,162 },163 });164165 return <MaterialReactTable table={table} />;166};167168const queryClient = new QueryClient();169170const ExampleWithReactQueryProvider = () => (171 //App.tsx or AppProviders file. Don't just wrap this component with QueryClientProvider! Wrap your whole App!172 <QueryClientProvider client={queryClient}>173 <Example />174 </QueryClientProvider>175);176177export default ExampleWithReactQueryProvider;178
View Extra Storybook Examples