@mini_7/material-react-table-mcp 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/README.md +68 -0
  2. package/data/MIGRATION.md +136 -0
  3. package/data/api.json +5379 -0
  4. package/data/examples/advanced.tsx +309 -0
  5. package/data/examples/aggregation-and-grouping.tsx +153 -0
  6. package/data/examples/aggregation-multi.tsx +105 -0
  7. package/data/examples/alternate-column-filtering.tsx +49 -0
  8. package/data/examples/alternate-detail-panel.tsx +81 -0
  9. package/data/examples/alternate-pagination.tsx +23 -0
  10. package/data/examples/basic.tsx +109 -0
  11. package/data/examples/chart-detail-panel.tsx +89 -0
  12. package/data/examples/column-actions-space.tsx +44 -0
  13. package/data/examples/column-alignment.tsx +70 -0
  14. package/data/examples/custom-column-actions.tsx +109 -0
  15. package/data/examples/custom-column-filtering-ui.tsx +89 -0
  16. package/data/examples/custom-headless.tsx +125 -0
  17. package/data/examples/custom-top-toolbar.tsx +88 -0
  18. package/data/examples/customize-display-columns.tsx +85 -0
  19. package/data/examples/customize-filter-components.tsx +101 -0
  20. package/data/examples/customize-filter-modes.tsx +73 -0
  21. package/data/examples/customize-filter-variants.tsx +116 -0
  22. package/data/examples/customize-global-filter-component.tsx +52 -0
  23. package/data/examples/customize-remove-column-grouping.tsx +94 -0
  24. package/data/examples/customize-row-selection.tsx +54 -0
  25. package/data/examples/customize-table-styles.tsx +100 -0
  26. package/data/examples/disable-column-actions.tsx +37 -0
  27. package/data/examples/disable-column-hiding.tsx +90 -0
  28. package/data/examples/disable-density-toggle.tsx +49 -0
  29. package/data/examples/dynamic-columns.tsx +189 -0
  30. package/data/examples/editing-crud-cell.tsx +377 -0
  31. package/data/examples/editing-crud-modal.tsx +370 -0
  32. package/data/examples/editing-crud-row.tsx +333 -0
  33. package/data/examples/editing-crud-table.tsx +369 -0
  34. package/data/examples/editing-crud-tree.tsx +416 -0
  35. package/data/examples/enable-cell-actions.tsx +76 -0
  36. package/data/examples/enable-click-to-copy.tsx +39 -0
  37. package/data/examples/enable-column-grouping.tsx +120 -0
  38. package/data/examples/enable-column-ordering.tsx +48 -0
  39. package/data/examples/enable-column-pinning.tsx +69 -0
  40. package/data/examples/enable-column-resizing.tsx +56 -0
  41. package/data/examples/enable-column-virtualization.tsx +27 -0
  42. package/data/examples/enable-detail-panel-conditionally.tsx +78 -0
  43. package/data/examples/enable-detail-panel-virtualized.tsx +80 -0
  44. package/data/examples/enable-expanding-tree.tsx +110 -0
  45. package/data/examples/enable-filter-facet-values.tsx +65 -0
  46. package/data/examples/enable-row-dragging.tsx +137 -0
  47. package/data/examples/enable-row-numbers-original.tsx +48 -0
  48. package/data/examples/enable-row-numbers-static.tsx +48 -0
  49. package/data/examples/enable-row-ordering.tsx +57 -0
  50. package/data/examples/enable-row-pinning-select.tsx +76 -0
  51. package/data/examples/enable-row-pinning-static.tsx +50 -0
  52. package/data/examples/enable-row-pinning-sticky.tsx +69 -0
  53. package/data/examples/enable-row-selection.tsx +96 -0
  54. package/data/examples/enable-row-virtualization.tsx +101 -0
  55. package/data/examples/enable-sticky-header.tsx +59 -0
  56. package/data/examples/expanding-tree-expanded.tsx +121 -0
  57. package/data/examples/expanding-tree-flat-parse.tsx +125 -0
  58. package/data/examples/expanding-tree-root-expanded.tsx +151 -0
  59. package/data/examples/export-to-csv.tsx +114 -0
  60. package/data/examples/export-to-pdf.tsx +104 -0
  61. package/data/examples/external-toolbar.tsx +108 -0
  62. package/data/examples/font-awesome-icons.tsx +106 -0
  63. package/data/examples/infinite-scrolling.tsx +208 -0
  64. package/data/examples/lazy-detail-panel.tsx +262 -0
  65. package/data/examples/lazy-sub-rows.tsx +190 -0
  66. package/data/examples/linear-progress.tsx +66 -0
  67. package/data/examples/loading.tsx +50 -0
  68. package/data/examples/localization-i18n-ar.tsx +65 -0
  69. package/data/examples/localization-i18n-az.tsx +60 -0
  70. package/data/examples/localization-i18n-bg.tsx +60 -0
  71. package/data/examples/localization-i18n-cs.tsx +60 -0
  72. package/data/examples/localization-i18n-da.tsx +60 -0
  73. package/data/examples/localization-i18n-de.tsx +60 -0
  74. package/data/examples/localization-i18n-el.tsx +60 -0
  75. package/data/examples/localization-i18n-en.tsx +60 -0
  76. package/data/examples/localization-i18n-es.tsx +60 -0
  77. package/data/examples/localization-i18n-et.tsx +60 -0
  78. package/data/examples/localization-i18n-fa.tsx +66 -0
  79. package/data/examples/localization-i18n-fi.tsx +60 -0
  80. package/data/examples/localization-i18n-fr.tsx +60 -0
  81. package/data/examples/localization-i18n-he.tsx +66 -0
  82. package/data/examples/localization-i18n-hr.tsx +60 -0
  83. package/data/examples/localization-i18n-hu.tsx +60 -0
  84. package/data/examples/localization-i18n-hy.tsx +65 -0
  85. package/data/examples/localization-i18n-id.tsx +60 -0
  86. package/data/examples/localization-i18n-it.tsx +60 -0
  87. package/data/examples/localization-i18n-ja.tsx +60 -0
  88. package/data/examples/localization-i18n-ko.tsx +60 -0
  89. package/data/examples/localization-i18n-nl.tsx +60 -0
  90. package/data/examples/localization-i18n-no.tsx +60 -0
  91. package/data/examples/localization-i18n-np.tsx +60 -0
  92. package/data/examples/localization-i18n-pl.tsx +60 -0
  93. package/data/examples/localization-i18n-pt-BR.tsx +60 -0
  94. package/data/examples/localization-i18n-pt.tsx +60 -0
  95. package/data/examples/localization-i18n-ro.tsx +60 -0
  96. package/data/examples/localization-i18n-ru.tsx +60 -0
  97. package/data/examples/localization-i18n-sk.tsx +60 -0
  98. package/data/examples/localization-i18n-sr-Cyrl-RS.tsx +60 -0
  99. package/data/examples/localization-i18n-sr-Latn-RS.tsx +60 -0
  100. package/data/examples/localization-i18n-sv.tsx +60 -0
  101. package/data/examples/localization-i18n-tr.tsx +60 -0
  102. package/data/examples/localization-i18n-uk.tsx +60 -0
  103. package/data/examples/localization-i18n-vi.tsx +60 -0
  104. package/data/examples/localization-i18n-zh-hans.tsx +60 -0
  105. package/data/examples/localization-i18n-zh-hant.tsx +60 -0
  106. package/data/examples/manual-selection.tsx +105 -0
  107. package/data/examples/minimal.tsx +78 -0
  108. package/data/examples/mui-theme.tsx +136 -0
  109. package/data/examples/multi-sorting.tsx +58 -0
  110. package/data/examples/persistent-state.tsx +184 -0
  111. package/data/examples/react-query.tsx +182 -0
  112. package/data/examples/remote.tsx +150 -0
  113. package/data/examples/row-actions-buttons.tsx +89 -0
  114. package/data/examples/row-actions-menu-items.tsx +64 -0
  115. package/data/examples/single-row-selection.tsx +98 -0
  116. package/data/examples/virtualized.tsx +138 -0
  117. package/data/guides/accessibility.md +119 -0
  118. package/data/guides/aggregation.md +210 -0
  119. package/data/guides/async-loading.md +132 -0
  120. package/data/guides/best-practices.md +363 -0
  121. package/data/guides/cell-actions.md +139 -0
  122. package/data/guides/click-to-copy.md +67 -0
  123. package/data/guides/column-actions.md +69 -0
  124. package/data/guides/column-filtering.md +366 -0
  125. package/data/guides/column-grouping.md +192 -0
  126. package/data/guides/column-hiding.md +133 -0
  127. package/data/guides/column-ordering-dnd.md +99 -0
  128. package/data/guides/column-pinning.md +72 -0
  129. package/data/guides/column-resizing.md +157 -0
  130. package/data/guides/column-size.md +162 -0
  131. package/data/guides/customize-components.md +395 -0
  132. package/data/guides/customize-icons.md +35 -0
  133. package/data/guides/data-columns.md +292 -0
  134. package/data/guides/density-toggle.md +46 -0
  135. package/data/guides/detail-panel.md +195 -0
  136. package/data/guides/display-columns.md +135 -0
  137. package/data/guides/editing.md +367 -0
  138. package/data/guides/expanding-sub-rows.md +194 -0
  139. package/data/guides/full-screen-toggle.md +63 -0
  140. package/data/guides/global-filtering.md +237 -0
  141. package/data/guides/localization.md +57 -0
  142. package/data/guides/memoization.md +157 -0
  143. package/data/guides/pagination.md +171 -0
  144. package/data/guides/row-actions.md +116 -0
  145. package/data/guides/row-numbers.md +24 -0
  146. package/data/guides/row-ordering-dnd.md +51 -0
  147. package/data/guides/row-pinning.md +96 -0
  148. package/data/guides/row-selection.md +237 -0
  149. package/data/guides/sorting.md +190 -0
  150. package/data/guides/state-management.md +165 -0
  151. package/data/guides/sticky-header.md +41 -0
  152. package/data/guides/table-event-listeners.md +103 -0
  153. package/data/guides/toolbar-customization.md +251 -0
  154. package/data/guides/virtualization.md +183 -0
  155. package/data/index.json +1930 -0
  156. package/data/reference/mrt-components.md +183 -0
  157. package/data/reference/mrt-hooks.md +125 -0
  158. package/data/skills/composable-components/SKILL.md +189 -0
  159. package/data/skills/customization/SKILL.md +190 -0
  160. package/data/skills/drag-and-drop-ordering/SKILL.md +227 -0
  161. package/data/skills/editing/SKILL.md +243 -0
  162. package/data/skills/filtering/SKILL.md +213 -0
  163. package/data/skills/getting-started/SKILL.md +213 -0
  164. package/data/skills/localization/SKILL.md +182 -0
  165. package/data/skills/migrate-v3-to-v4/SKILL.md +161 -0
  166. package/data/skills/state-and-server-data/SKILL.md +200 -0
  167. package/data/skills/virtualization/SKILL.md +203 -0
  168. package/dist/data.js +137 -0
  169. package/dist/index.js +232 -0
  170. package/package.json +50 -0
@@ -0,0 +1,370 @@
1
+ import { lazy, Suspense, useMemo, useState } from 'react';
2
+ import {
3
+ MRT_EditActionButtons,
4
+ MaterialReactTable,
5
+ // createRow,
6
+ type MRT_ColumnDef,
7
+ type MRT_Row,
8
+ type MRT_TableOptions,
9
+ useMaterialReactTable,
10
+ } from 'material-react-table';
11
+ import {
12
+ Box,
13
+ Button,
14
+ DialogActions,
15
+ DialogContent,
16
+ DialogTitle,
17
+ IconButton,
18
+ Tooltip,
19
+ } from '@mui/material';
20
+ import {
21
+ QueryClient,
22
+ QueryClientProvider,
23
+ useMutation,
24
+ useQuery,
25
+ useQueryClient,
26
+ } from '@tanstack/react-query';
27
+ import { type User, fakeData, usStates } from './makeData';
28
+ import EditIcon from '@mui/icons-material/Edit';
29
+ import DeleteIcon from '@mui/icons-material/Delete';
30
+
31
+ const Example = () => {
32
+ const [validationErrors, setValidationErrors] = useState<
33
+ Record<string, string | undefined>
34
+ >({});
35
+
36
+ const columns = useMemo<MRT_ColumnDef<User>[]>(
37
+ () => [
38
+ {
39
+ accessorKey: 'id',
40
+ header: 'Id',
41
+ enableEditing: false,
42
+ size: 80,
43
+ },
44
+ {
45
+ accessorKey: 'firstName',
46
+ header: 'First Name',
47
+ muiEditTextFieldProps: {
48
+ required: true,
49
+ error: !!validationErrors?.firstName,
50
+ helperText: validationErrors?.firstName,
51
+ //remove any previous validation errors when user focuses on the input
52
+ onFocus: () =>
53
+ setValidationErrors({
54
+ ...validationErrors,
55
+ firstName: undefined,
56
+ }),
57
+ //optionally add validation checking for onBlur or onChange
58
+ },
59
+ },
60
+ {
61
+ accessorKey: 'lastName',
62
+ header: 'Last Name',
63
+ muiEditTextFieldProps: {
64
+ required: true,
65
+ error: !!validationErrors?.lastName,
66
+ helperText: validationErrors?.lastName,
67
+ //remove any previous validation errors when user focuses on the input
68
+ onFocus: () =>
69
+ setValidationErrors({
70
+ ...validationErrors,
71
+ lastName: undefined,
72
+ }),
73
+ },
74
+ },
75
+ {
76
+ accessorKey: 'email',
77
+ header: 'Email',
78
+ muiEditTextFieldProps: {
79
+ type: 'email',
80
+ required: true,
81
+ error: !!validationErrors?.email,
82
+ helperText: validationErrors?.email,
83
+ //remove any previous validation errors when user focuses on the input
84
+ onFocus: () =>
85
+ setValidationErrors({
86
+ ...validationErrors,
87
+ email: undefined,
88
+ }),
89
+ },
90
+ },
91
+ {
92
+ accessorKey: 'state',
93
+ header: 'State',
94
+ editVariant: 'select',
95
+ editSelectOptions: usStates,
96
+ muiEditTextFieldProps: {
97
+ select: true,
98
+ error: !!validationErrors?.state,
99
+ helperText: validationErrors?.state,
100
+ },
101
+ },
102
+ ],
103
+ [validationErrors],
104
+ );
105
+
106
+ //call CREATE hook
107
+ const { mutateAsync: createUser, isPending: isCreatingUser } =
108
+ useCreateUser();
109
+ //call READ hook
110
+ const {
111
+ data: fetchedUsers = [],
112
+ isError: isLoadingUsersError,
113
+ isFetching: isFetchingUsers,
114
+ isLoading: isLoadingUsers,
115
+ } = useGetUsers();
116
+ //call UPDATE hook
117
+ const { mutateAsync: updateUser, isPending: isUpdatingUser } =
118
+ useUpdateUser();
119
+ //call DELETE hook
120
+ const { mutateAsync: deleteUser, isPending: isDeletingUser } =
121
+ useDeleteUser();
122
+
123
+ //CREATE action
124
+ const handleCreateUser: MRT_TableOptions<User>['onCreatingRowSave'] = async ({
125
+ values,
126
+ table,
127
+ }) => {
128
+ const newValidationErrors = validateUser(values);
129
+ if (Object.values(newValidationErrors).some((error) => error)) {
130
+ setValidationErrors(newValidationErrors);
131
+ return;
132
+ }
133
+ setValidationErrors({});
134
+ await createUser(values);
135
+ table.setCreatingRow(null); //exit creating mode
136
+ };
137
+
138
+ //UPDATE action
139
+ const handleSaveUser: MRT_TableOptions<User>['onEditingRowSave'] = async ({
140
+ values,
141
+ table,
142
+ }) => {
143
+ const newValidationErrors = validateUser(values);
144
+ if (Object.values(newValidationErrors).some((error) => error)) {
145
+ setValidationErrors(newValidationErrors);
146
+ return;
147
+ }
148
+ setValidationErrors({});
149
+ await updateUser(values);
150
+ table.setEditingRow(null); //exit editing mode
151
+ };
152
+
153
+ //DELETE action
154
+ const openDeleteConfirmModal = (row: MRT_Row<User>) => {
155
+ if (window.confirm('Are you sure you want to delete this user?')) {
156
+ deleteUser(row.original.id);
157
+ }
158
+ };
159
+
160
+ const table = useMaterialReactTable({
161
+ columns,
162
+ data: fetchedUsers,
163
+ createDisplayMode: 'modal', //default ('row', and 'custom' are also available)
164
+ editDisplayMode: 'modal', //default ('row', 'cell', 'table', and 'custom' are also available)
165
+ enableEditing: true,
166
+ getRowId: (row) => row.id,
167
+ muiToolbarAlertBannerProps: isLoadingUsersError
168
+ ? {
169
+ color: 'error',
170
+ children: 'Error loading data',
171
+ }
172
+ : undefined,
173
+ muiTableContainerProps: {
174
+ sx: {
175
+ minHeight: '500px',
176
+ },
177
+ },
178
+ onCreatingRowCancel: () => setValidationErrors({}),
179
+ onCreatingRowSave: handleCreateUser,
180
+ onEditingRowCancel: () => setValidationErrors({}),
181
+ onEditingRowSave: handleSaveUser,
182
+ //optionally customize modal content
183
+ renderCreateRowDialogContent: ({ table, row, internalEditComponents }) => (
184
+ <>
185
+ <DialogTitle variant="h3">Create New User</DialogTitle>
186
+ <DialogContent
187
+ sx={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}
188
+ >
189
+ {internalEditComponents} {/* or render custom edit components here */}
190
+ </DialogContent>
191
+ <DialogActions>
192
+ <MRT_EditActionButtons variant="text" table={table} row={row} />
193
+ </DialogActions>
194
+ </>
195
+ ),
196
+ //optionally customize modal content
197
+ renderEditRowDialogContent: ({ table, row, internalEditComponents }) => (
198
+ <>
199
+ <DialogTitle variant="h3">Edit User</DialogTitle>
200
+ <DialogContent
201
+ sx={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}
202
+ >
203
+ {internalEditComponents} {/* or render custom edit components here */}
204
+ </DialogContent>
205
+ <DialogActions>
206
+ <MRT_EditActionButtons variant="text" table={table} row={row} />
207
+ </DialogActions>
208
+ </>
209
+ ),
210
+ renderRowActions: ({ row, table }) => (
211
+ <Box sx={{ display: 'flex', gap: '1rem' }}>
212
+ <Tooltip title="Edit">
213
+ <IconButton onClick={() => table.setEditingRow(row)}>
214
+ <EditIcon />
215
+ </IconButton>
216
+ </Tooltip>
217
+ <Tooltip title="Delete">
218
+ <IconButton color="error" onClick={() => openDeleteConfirmModal(row)}>
219
+ <DeleteIcon />
220
+ </IconButton>
221
+ </Tooltip>
222
+ </Box>
223
+ ),
224
+ renderTopToolbarCustomActions: ({ table }) => (
225
+ <Button
226
+ variant="contained"
227
+ onClick={() => {
228
+ table.setCreatingRow(true); //simplest way to open the create row modal with no default values
229
+ //or you can pass in a row object to set default values with the `createRow` helper function
230
+ // table.setCreatingRow(
231
+ // createRow(table, {
232
+ // //optionally pass in default values for the new row, useful for nested data or other complex scenarios
233
+ // }),
234
+ // );
235
+ }}
236
+ >
237
+ Create New User
238
+ </Button>
239
+ ),
240
+ state: {
241
+ isLoading: isLoadingUsers,
242
+ isSaving: isCreatingUser || isUpdatingUser || isDeletingUser,
243
+ showAlertBanner: isLoadingUsersError,
244
+ showProgressBars: isFetchingUsers,
245
+ },
246
+ });
247
+
248
+ return <MaterialReactTable table={table} />;
249
+ };
250
+
251
+ //CREATE hook (post new user to api)
252
+ function useCreateUser() {
253
+ const queryClient = useQueryClient();
254
+ return useMutation({
255
+ mutationFn: async (user: User) => {
256
+ //send api update request here
257
+ await new Promise((resolve) => setTimeout(resolve, 1000)); //fake api call
258
+ return Promise.resolve();
259
+ },
260
+ //client side optimistic update
261
+ onMutate: (newUserInfo: User) => {
262
+ queryClient.setQueryData(
263
+ ['users'],
264
+ (prevUsers: any) =>
265
+ [
266
+ ...prevUsers,
267
+ {
268
+ ...newUserInfo,
269
+ id: (Math.random() + 1).toString(36).substring(7),
270
+ },
271
+ ] as User[],
272
+ );
273
+ },
274
+ // onSettled: () => queryClient.invalidateQueries({ queryKey: ['users'] }), //refetch users after mutation, disabled for demo
275
+ });
276
+ }
277
+
278
+ //READ hook (get users from api)
279
+ function useGetUsers() {
280
+ return useQuery<User[]>({
281
+ queryKey: ['users'],
282
+ queryFn: async () => {
283
+ //send api request here
284
+ await new Promise((resolve) => setTimeout(resolve, 1000)); //fake api call
285
+ return Promise.resolve(fakeData);
286
+ },
287
+ refetchOnWindowFocus: false,
288
+ });
289
+ }
290
+
291
+ //UPDATE hook (put user in api)
292
+ function useUpdateUser() {
293
+ const queryClient = useQueryClient();
294
+ return useMutation({
295
+ mutationFn: async (user: User) => {
296
+ //send api update request here
297
+ await new Promise((resolve) => setTimeout(resolve, 1000)); //fake api call
298
+ return Promise.resolve();
299
+ },
300
+ //client side optimistic update
301
+ onMutate: (newUserInfo: User) => {
302
+ queryClient.setQueryData(['users'], (prevUsers: any) =>
303
+ prevUsers?.map((prevUser: User) =>
304
+ prevUser.id === newUserInfo.id ? newUserInfo : prevUser,
305
+ ),
306
+ );
307
+ },
308
+ // onSettled: () => queryClient.invalidateQueries({ queryKey: ['users'] }), //refetch users after mutation, disabled for demo
309
+ });
310
+ }
311
+
312
+ //DELETE hook (delete user in api)
313
+ function useDeleteUser() {
314
+ const queryClient = useQueryClient();
315
+ return useMutation({
316
+ mutationFn: async (userId: string) => {
317
+ //send api update request here
318
+ await new Promise((resolve) => setTimeout(resolve, 1000)); //fake api call
319
+ return Promise.resolve();
320
+ },
321
+ //client side optimistic update
322
+ onMutate: (userId: string) => {
323
+ queryClient.setQueryData(['users'], (prevUsers: any) =>
324
+ prevUsers?.filter((user: User) => user.id !== userId),
325
+ );
326
+ },
327
+ // onSettled: () => queryClient.invalidateQueries({ queryKey: ['users'] }), //refetch users after mutation, disabled for demo
328
+ });
329
+ }
330
+
331
+ //react query setup in App.tsx
332
+ const ReactQueryDevtoolsProduction = lazy(() =>
333
+ import('@tanstack/react-query-devtools/build/modern/production.js').then(
334
+ (d) => ({
335
+ default: d.ReactQueryDevtools,
336
+ }),
337
+ ),
338
+ );
339
+
340
+ const queryClient = new QueryClient();
341
+
342
+ export default function App() {
343
+ return (
344
+ <QueryClientProvider client={queryClient}>
345
+ <Example />
346
+ <Suspense fallback={null}>
347
+ <ReactQueryDevtoolsProduction />
348
+ </Suspense>
349
+ </QueryClientProvider>
350
+ );
351
+ }
352
+
353
+ const validateRequired = (value: string) => !!value.length;
354
+ const validateEmail = (email: string) =>
355
+ !!email.length &&
356
+ email
357
+ .toLowerCase()
358
+ .match(
359
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
360
+ );
361
+
362
+ function validateUser(user: User) {
363
+ return {
364
+ firstName: !validateRequired(user.firstName)
365
+ ? 'First Name is Required'
366
+ : '',
367
+ lastName: !validateRequired(user.lastName) ? 'Last Name is Required' : '',
368
+ email: !validateEmail(user.email) ? 'Incorrect Email Format' : '',
369
+ };
370
+ }
@@ -0,0 +1,333 @@
1
+ import { lazy, Suspense, useMemo, useState } from 'react';
2
+ import {
3
+ MaterialReactTable,
4
+ // createRow,
5
+ type MRT_ColumnDef,
6
+ type MRT_Row,
7
+ type MRT_TableOptions,
8
+ useMaterialReactTable,
9
+ } from 'material-react-table';
10
+ import { Box, Button, IconButton, Tooltip } from '@mui/material';
11
+ import {
12
+ QueryClient,
13
+ QueryClientProvider,
14
+ useMutation,
15
+ useQuery,
16
+ useQueryClient,
17
+ } from '@tanstack/react-query';
18
+ import { type User, fakeData, usStates } from './makeData';
19
+ import EditIcon from '@mui/icons-material/Edit';
20
+ import DeleteIcon from '@mui/icons-material/Delete';
21
+
22
+ const Example = () => {
23
+ const [validationErrors, setValidationErrors] = useState<
24
+ Record<string, string | undefined>
25
+ >({});
26
+
27
+ const columns = useMemo<MRT_ColumnDef<User>[]>(
28
+ () => [
29
+ {
30
+ accessorKey: 'id',
31
+ header: 'Id',
32
+ enableEditing: false,
33
+ size: 80,
34
+ },
35
+ {
36
+ accessorKey: 'firstName',
37
+ header: 'First Name',
38
+ muiEditTextFieldProps: {
39
+ required: true,
40
+ error: !!validationErrors?.firstName,
41
+ helperText: validationErrors?.firstName,
42
+ //remove any previous validation errors when user focuses on the input
43
+ onFocus: () =>
44
+ setValidationErrors({
45
+ ...validationErrors,
46
+ firstName: undefined,
47
+ }),
48
+ //optionally add validation checking for onBlur or onChange
49
+ },
50
+ },
51
+ {
52
+ accessorKey: 'lastName',
53
+ header: 'Last Name',
54
+ muiEditTextFieldProps: {
55
+ required: true,
56
+ error: !!validationErrors?.lastName,
57
+ helperText: validationErrors?.lastName,
58
+ //remove any previous validation errors when user focuses on the input
59
+ onFocus: () =>
60
+ setValidationErrors({
61
+ ...validationErrors,
62
+ lastName: undefined,
63
+ }),
64
+ },
65
+ },
66
+ {
67
+ accessorKey: 'email',
68
+ header: 'Email',
69
+ muiEditTextFieldProps: {
70
+ type: 'email',
71
+ required: true,
72
+ error: !!validationErrors?.email,
73
+ helperText: validationErrors?.email,
74
+ //remove any previous validation errors when user focuses on the input
75
+ onFocus: () =>
76
+ setValidationErrors({
77
+ ...validationErrors,
78
+ email: undefined,
79
+ }),
80
+ },
81
+ },
82
+ {
83
+ accessorKey: 'state',
84
+ header: 'State',
85
+ editVariant: 'select',
86
+ editSelectOptions: usStates,
87
+ muiEditTextFieldProps: {
88
+ select: true,
89
+ error: !!validationErrors?.state,
90
+ helperText: validationErrors?.state,
91
+ },
92
+ },
93
+ ],
94
+ [validationErrors],
95
+ );
96
+
97
+ //call CREATE hook
98
+ const { mutateAsync: createUser, isPending: isCreatingUser } =
99
+ useCreateUser();
100
+ //call READ hook
101
+ const {
102
+ data: fetchedUsers = [],
103
+ isError: isLoadingUsersError,
104
+ isFetching: isFetchingUsers,
105
+ isLoading: isLoadingUsers,
106
+ } = useGetUsers();
107
+ //call UPDATE hook
108
+ const { mutateAsync: updateUser, isPending: isUpdatingUser } =
109
+ useUpdateUser();
110
+ //call DELETE hook
111
+ const { mutateAsync: deleteUser, isPending: isDeletingUser } =
112
+ useDeleteUser();
113
+
114
+ //CREATE action
115
+ const handleCreateUser: MRT_TableOptions<User>['onCreatingRowSave'] = async ({
116
+ values,
117
+ table,
118
+ }) => {
119
+ const newValidationErrors = validateUser(values);
120
+ if (Object.values(newValidationErrors).some((error) => error)) {
121
+ setValidationErrors(newValidationErrors);
122
+ return;
123
+ }
124
+ setValidationErrors({});
125
+ await createUser(values);
126
+ table.setCreatingRow(null); //exit creating mode
127
+ };
128
+
129
+ //UPDATE action
130
+ const handleSaveUser: MRT_TableOptions<User>['onEditingRowSave'] = async ({
131
+ values,
132
+ table,
133
+ }) => {
134
+ const newValidationErrors = validateUser(values);
135
+ if (Object.values(newValidationErrors).some((error) => error)) {
136
+ setValidationErrors(newValidationErrors);
137
+ return;
138
+ }
139
+ setValidationErrors({});
140
+ await updateUser(values);
141
+ table.setEditingRow(null); //exit editing mode
142
+ };
143
+
144
+ //DELETE action
145
+ const openDeleteConfirmModal = (row: MRT_Row<User>) => {
146
+ if (window.confirm('Are you sure you want to delete this user?')) {
147
+ deleteUser(row.original.id);
148
+ }
149
+ };
150
+
151
+ const table = useMaterialReactTable({
152
+ columns,
153
+ data: fetchedUsers,
154
+ createDisplayMode: 'row', // ('modal', and 'custom' are also available)
155
+ editDisplayMode: 'row', // ('modal', 'cell', 'table', and 'custom' are also available)
156
+ enableEditing: true,
157
+ getRowId: (row) => row.id,
158
+ muiToolbarAlertBannerProps: isLoadingUsersError
159
+ ? {
160
+ color: 'error',
161
+ children: 'Error loading data',
162
+ }
163
+ : undefined,
164
+ muiTableContainerProps: {
165
+ sx: {
166
+ minHeight: '500px',
167
+ },
168
+ },
169
+ onCreatingRowCancel: () => setValidationErrors({}),
170
+ onCreatingRowSave: handleCreateUser,
171
+ onEditingRowCancel: () => setValidationErrors({}),
172
+ onEditingRowSave: handleSaveUser,
173
+ renderRowActions: ({ row, table }) => (
174
+ <Box sx={{ display: 'flex', gap: '1rem' }}>
175
+ <Tooltip title="Edit">
176
+ <IconButton onClick={() => table.setEditingRow(row)}>
177
+ <EditIcon />
178
+ </IconButton>
179
+ </Tooltip>
180
+ <Tooltip title="Delete">
181
+ <IconButton color="error" onClick={() => openDeleteConfirmModal(row)}>
182
+ <DeleteIcon />
183
+ </IconButton>
184
+ </Tooltip>
185
+ </Box>
186
+ ),
187
+ renderTopToolbarCustomActions: ({ table }) => (
188
+ <Button
189
+ variant="contained"
190
+ onClick={() => {
191
+ table.setCreatingRow(true); //simplest way to open the create row modal with no default values
192
+ //or you can pass in a row object to set default values with the `createRow` helper function
193
+ // table.setCreatingRow(
194
+ // createRow(table, {
195
+ // //optionally pass in default values for the new row, useful for nested data or other complex scenarios
196
+ // }),
197
+ // );
198
+ }}
199
+ >
200
+ Create New User
201
+ </Button>
202
+ ),
203
+ state: {
204
+ isLoading: isLoadingUsers,
205
+ isSaving: isCreatingUser || isUpdatingUser || isDeletingUser,
206
+ showAlertBanner: isLoadingUsersError,
207
+ showProgressBars: isFetchingUsers,
208
+ },
209
+ });
210
+
211
+ return <MaterialReactTable table={table} />;
212
+ };
213
+
214
+ //CREATE hook (post new user to api)
215
+ function useCreateUser() {
216
+ const queryClient = useQueryClient();
217
+ return useMutation({
218
+ mutationFn: async (user: User) => {
219
+ //send api update request here
220
+ await new Promise((resolve) => setTimeout(resolve, 1000)); //fake api call
221
+ return Promise.resolve();
222
+ },
223
+ //client side optimistic update
224
+ onMutate: (newUserInfo: User) => {
225
+ queryClient.setQueryData(
226
+ ['users'],
227
+ (prevUsers: any) =>
228
+ [
229
+ ...prevUsers,
230
+ {
231
+ ...newUserInfo,
232
+ id: (Math.random() + 1).toString(36).substring(7),
233
+ },
234
+ ] as User[],
235
+ );
236
+ },
237
+ // onSettled: () => queryClient.invalidateQueries({ queryKey: ['users'] }), //refetch users after mutation, disabled for demo
238
+ });
239
+ }
240
+
241
+ //READ hook (get users from api)
242
+ function useGetUsers() {
243
+ return useQuery<User[]>({
244
+ queryKey: ['users'],
245
+ queryFn: async () => {
246
+ //send api request here
247
+ await new Promise((resolve) => setTimeout(resolve, 1000)); //fake api call
248
+ return Promise.resolve(fakeData);
249
+ },
250
+ refetchOnWindowFocus: false,
251
+ });
252
+ }
253
+
254
+ //UPDATE hook (put user in api)
255
+ function useUpdateUser() {
256
+ const queryClient = useQueryClient();
257
+ return useMutation({
258
+ mutationFn: async (user: User) => {
259
+ //send api update request here
260
+ await new Promise((resolve) => setTimeout(resolve, 1000)); //fake api call
261
+ return Promise.resolve();
262
+ },
263
+ //client side optimistic update
264
+ onMutate: (newUserInfo: User) => {
265
+ queryClient.setQueryData(['users'], (prevUsers: any) =>
266
+ prevUsers?.map((prevUser: User) =>
267
+ prevUser.id === newUserInfo.id ? newUserInfo : prevUser,
268
+ ),
269
+ );
270
+ },
271
+ // onSettled: () => queryClient.invalidateQueries({ queryKey: ['users'] }), //refetch users after mutation, disabled for demo
272
+ });
273
+ }
274
+
275
+ //DELETE hook (delete user in api)
276
+ function useDeleteUser() {
277
+ const queryClient = useQueryClient();
278
+ return useMutation({
279
+ mutationFn: async (userId: string) => {
280
+ //send api update request here
281
+ await new Promise((resolve) => setTimeout(resolve, 1000)); //fake api call
282
+ return Promise.resolve();
283
+ },
284
+ //client side optimistic update
285
+ onMutate: (userId: string) => {
286
+ queryClient.setQueryData(['users'], (prevUsers: any) =>
287
+ prevUsers?.filter((user: User) => user.id !== userId),
288
+ );
289
+ },
290
+ // onSettled: () => queryClient.invalidateQueries({ queryKey: ['users'] }), //refetch users after mutation, disabled for demo
291
+ });
292
+ }
293
+
294
+ //react query setup in App.tsx
295
+ const ReactQueryDevtoolsProduction = lazy(() =>
296
+ import('@tanstack/react-query-devtools/build/modern/production.js').then(
297
+ (d) => ({
298
+ default: d.ReactQueryDevtools,
299
+ }),
300
+ ),
301
+ );
302
+
303
+ const queryClient = new QueryClient();
304
+
305
+ export default function App() {
306
+ return (
307
+ <QueryClientProvider client={queryClient}>
308
+ <Example />
309
+ <Suspense fallback={null}>
310
+ <ReactQueryDevtoolsProduction />
311
+ </Suspense>
312
+ </QueryClientProvider>
313
+ );
314
+ }
315
+
316
+ const validateRequired = (value: string) => !!value.length;
317
+ const validateEmail = (email: string) =>
318
+ !!email.length &&
319
+ email
320
+ .toLowerCase()
321
+ .match(
322
+ /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
323
+ );
324
+
325
+ function validateUser(user: User) {
326
+ return {
327
+ firstName: !validateRequired(user.firstName)
328
+ ? 'First Name is Required'
329
+ : '',
330
+ lastName: !validateRequired(user.lastName) ? 'Last Name is Required' : '',
331
+ email: !validateEmail(user.email) ? 'Incorrect Email Format' : '',
332
+ };
333
+ }