MRT logoMaterial React Table

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.

CRUD Examples
More Examples

Demo

Open StackblitzOpen Code SandboxOpen on GitHub
0-0 of 0

Source Code

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 V5
19
20//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};
27
28type User = {
29 firstName: string;
30 lastName: string;
31 address: string;
32 state: string;
33 phoneNumber: string;
34 lastLogin: Date;
35};
36
37const 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;
45
46const Example = () => {
47 //manage our own state for stuff we want to pass to the API
48 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 });
57
58 //if using dynamic columns that are loaded after table instance creation, we will need to manage the column order state ourselves
59 //UPDATE: No longer needed as of v2.10.0
60 // const [columnOrder, setColumnOrder] = useState<MRT_ColumnOrderState>([]);
61
62 //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 different
65 isError,
66 isRefetching,
67 isLoading,
68 refetch,
69 } = useQuery<UserApiResponse>({
70 queryKey: [
71 'users-list',
72 {
73 columnFilters, //refetch when columnFilters changes
74 globalFilter, //refetch when globalFilter changes
75 pagination, //refetch when pagination changes
76 sorting, //refetch when sorting changes
77 },
78 ],
79 queryFn: async () => {
80 const fetchURL = new URL('/api/data', location.origin); // nextjs api route
81
82 //read our state and pass it to the API as query params
83 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 ?? []));
91
92 //use whatever fetch library you want, fetch, axios, etc
93 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 page
98 });
99
100 //create columns from data
101 const columns = useMemo<MRT_ColumnDef<User>[]>(
102 () =>
103 data.length
104 ? 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 );
112
113 //UPDATE: No longer needed as of v2.10.0
114 // useEffect(() => {
115 // //if using dynamic columns that are loaded after table instance creation, we will need to set the column order state ourselves
116 // setColumnOrder(columns.map((column) => column.id!));
117 // }, [columns]);
118
119 const table = useMaterialReactTable({
120 columns,
121 data,
122 enableRowSelection: true,
123 initialState: { showColumnFilters: true },
124 manualFiltering: true, //turn off built-in client-side filtering
125 manualPagination: true, //turn off built-in client-side pagination
126 manualSorting: true, //turn off built-in client-side sorting
127 //give loading spinner somewhere to go while loading
128 muiTableBodyProps: {
129 children: isLoading ? (
130 <tr style={{ height: '200px' }}>
131 <td />
132 </tr>
133 ) : undefined,
134 },
135 muiToolbarAlertBannerProps: isError
136 ? {
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 });
165
166 return <MaterialReactTable table={table} />;
167};
168
169//react query setup in App.tsx
170const ReactQueryDevtoolsProduction = lazy(() =>
171 import('@tanstack/react-query-devtools/build/modern/production.js').then(
172 (d) => ({
173 default: d.ReactQueryDevtools,
174 }),
175 ),
176);
177
178const queryClient = new QueryClient();
179
180export 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