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 { useEffect, 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 const [columnOrder, setColumnOrder] = useState<MRT_ColumnOrderState>([]);6061 //consider storing this code in a custom hook (i.e useFetchUsers)62 const {63 data: { data = [], meta } = {}, //your data and api response will probably be different64 isError,65 isRefetching,66 isLoading,67 refetch,68 } = useQuery<UserApiResponse>({69 queryKey: [70 'table-data',71 columnFilters, //refetch when columnFilters changes72 globalFilter, //refetch when globalFilter changes73 pagination.pageIndex, //refetch when pagination.pageIndex changes74 pagination.pageSize, //refetch when pagination.pageSize changes75 sorting, //refetch when sorting changes76 ],77 queryFn: async () => {78 const fetchURL = new URL(79 '/api/data',80 process.env.NODE_ENV === 'production'81 ? 'https://www.material-react-table.com'82 : 'http://localhost:3000',83 );8485 //read our state and pass it to the API as query params86 fetchURL.searchParams.set(87 'start',88 `${pagination.pageIndex * pagination.pageSize}`,89 );90 fetchURL.searchParams.set('size', `${pagination.pageSize}`);91 fetchURL.searchParams.set('filters', JSON.stringify(columnFilters ?? []));92 fetchURL.searchParams.set('globalFilter', globalFilter ?? '');93 fetchURL.searchParams.set('sorting', JSON.stringify(sorting ?? []));9495 //use whatever fetch library you want, fetch, axios, etc96 const response = await fetch(fetchURL.href);97 const json = (await response.json()) as UserApiResponse;98 return json;99 },100 placeholderData: keepPreviousData, //don't go to 0 rows when refetching or paginating to next page101 });102103 //create columns from data104 const columns = useMemo<MRT_ColumnDef<User>[]>(105 () =>106 data.length107 ? Object.keys(data[0]).map((columnId) => ({108 header: columnNames[columnId as keyof User] ?? columnId,109 accessorKey: columnId,110 id: columnId,111 }))112 : [],113 [data],114 );115116 useEffect(() => {117 //if using dynamic columns that are loaded after table instance creation, we will need to set the column order state ourselves118 setColumnOrder(columns.map((column) => column.id!));119 }, [columns]);120121 const table = useMaterialReactTable({122 columns,123 data,124 initialState: { showColumnFilters: true },125 manualFiltering: true, //turn off built-in client-side filtering126 manualPagination: true, //turn off built-in client-side pagination127 manualSorting: true, //turn off built-in client-side sorting128 //give loading spinner somewhere to go while loading129 muiTableBodyProps: {130 children: isLoading ? (131 <tr style={{ height: '200px' }}>132 <td />133 </tr>134 ) : undefined,135 },136 muiToolbarAlertBannerProps: isError137 ? {138 color: 'error',139 children: 'Error loading data',140 }141 : undefined,142 onColumnFiltersChange: setColumnFilters,143 onColumnOrderChange: setColumnOrder,144 onGlobalFilterChange: setGlobalFilter,145 onPaginationChange: setPagination,146 onSortingChange: setSorting,147 renderTopToolbarCustomActions: () => (148 <Tooltip arrow title="Refresh Data">149 <IconButton onClick={() => refetch()}>150 <RefreshIcon />151 </IconButton>152 </Tooltip>153 ),154 rowCount: meta?.totalRowCount ?? 0,155 state: {156 columnFilters,157 columnOrder,158 globalFilter,159 isLoading,160 pagination,161 showAlertBanner: isError,162 showProgressBars: isRefetching,163 sorting,164 },165 });166167 return <MaterialReactTable table={table} />;168};169170const queryClient = new QueryClient();171172const ExampleWithReactQueryProvider = () => (173 //App.tsx or AppProviders file. Don't just wrap this component with QueryClientProvider! Wrap your whole App!174 <QueryClientProvider client={queryClient}>175 <Example />176 </QueryClientProvider>177);178179export default ExampleWithReactQueryProvider;180
View Extra Storybook Examples