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 { 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 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 'table-data',
72 columnFilters, //refetch when columnFilters changes
73 globalFilter, //refetch when globalFilter changes
74 pagination.pageIndex, //refetch when pagination.pageIndex changes
75 pagination.pageSize, //refetch when pagination.pageSize changes
76 sorting, //refetch when sorting changes
77 ],
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 );
85
86 //read our state and pass it to the API as query params
87 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 ?? []));
95
96 //use whatever fetch library you want, fetch, axios, etc
97 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 page
102 });
103
104 //create columns from data
105 const columns = useMemo<MRT_ColumnDef<User>[]>(
106 () =>
107 data.length
108 ? 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 );
116
117 //UPDATE: No longer needed as of v2.10.0
118 // useEffect(() => {
119 // //if using dynamic columns that are loaded after table instance creation, we will need to set the column order state ourselves
120 // setColumnOrder(columns.map((column) => column.id!));
121 // }, [columns]);
122
123 const table = useMaterialReactTable({
124 columns,
125 data,
126 enableRowSelection: true,
127 initialState: { showColumnFilters: true },
128 manualFiltering: true, //turn off built-in client-side filtering
129 manualPagination: true, //turn off built-in client-side pagination
130 manualSorting: true, //turn off built-in client-side sorting
131 //give loading spinner somewhere to go while loading
132 muiTableBodyProps: {
133 children: isLoading ? (
134 <tr style={{ height: '200px' }}>
135 <td />
136 </tr>
137 ) : undefined,
138 },
139 muiToolbarAlertBannerProps: isError
140 ? {
141 color: 'error',
142 children: 'Error loading data',
143 }
144 : undefined,
145 onColumnFiltersChange: setColumnFilters,
146 // onColumnOrderChange: setColumnOrder,
147 onGlobalFilterChange: setGlobalFilter,
148 onPaginationChange: setPagination,
149 onSortingChange: setSorting,
150 renderTopToolbarCustomActions: () => (
151 <Tooltip arrow title="Refresh Data">
152 <IconButton onClick={() => refetch()}>
153 <RefreshIcon />
154 </IconButton>
155 </Tooltip>
156 ),
157 rowCount: meta?.totalRowCount ?? 0,
158 state: {
159 columnFilters,
160 // columnOrder,
161 globalFilter,
162 isLoading,
163 pagination,
164 showAlertBanner: isError,
165 showProgressBars: isRefetching,
166 sorting,
167 },
168 });
169
170 return <MaterialReactTable table={table} />;
171};
172
173const queryClient = new QueryClient();
174
175const ExampleWithReactQueryProvider = () => (
176 //App.tsx or AppProviders file. Don't just wrap this component with QueryClientProvider! Wrap your whole App!
177 <QueryClientProvider client={queryClient}>
178 <Example />
179 </QueryClientProvider>
180);
181
182export default ExampleWithReactQueryProvider;
183

View Extra Storybook Examples