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('/api/data', location.origin);
80
81 //read our state and pass it to the API as query params
82 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 ?? []));
90
91 //use whatever fetch library you want, fetch, axios, etc
92 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 page
97 });
98
99 //create columns from data
100 const columns = useMemo<MRT_ColumnDef<User>[]>(
101 () =>
102 data.length
103 ? 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 );
111
112 //UPDATE: No longer needed as of v2.10.0
113 // useEffect(() => {
114 // //if using dynamic columns that are loaded after table instance creation, we will need to set the column order state ourselves
115 // setColumnOrder(columns.map((column) => column.id!));
116 // }, [columns]);
117
118 const table = useMaterialReactTable({
119 columns,
120 data,
121 enableRowSelection: true,
122 initialState: { showColumnFilters: true },
123 manualFiltering: true, //turn off built-in client-side filtering
124 manualPagination: true, //turn off built-in client-side pagination
125 manualSorting: true, //turn off built-in client-side sorting
126 //give loading spinner somewhere to go while loading
127 muiTableBodyProps: {
128 children: isLoading ? (
129 <tr style={{ height: '200px' }}>
130 <td />
131 </tr>
132 ) : undefined,
133 },
134 muiToolbarAlertBannerProps: isError
135 ? {
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 });
164
165 return <MaterialReactTable table={table} />;
166};
167
168const queryClient = new QueryClient();
169
170const 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);
176
177export default ExampleWithReactQueryProvider;
178

View Extra Storybook Examples