@lminii/material-react-table-mcp 4.0.1

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