From 7f4cbb7f7a8449b8a2f6162352938149cf8d5ebc Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Sun, 26 May 2024 12:27:19 +0300 Subject: [PATCH 01/18] feat(useDataGrid): enhance data grid manipulation with CRUD operations and state handling --- packages/mui/src/hooks/useDataGrid/index.ts | 106 ++++++++++++++------ 1 file changed, 76 insertions(+), 30 deletions(-) diff --git a/packages/mui/src/hooks/useDataGrid/index.ts b/packages/mui/src/hooks/useDataGrid/index.ts index 6dbcc8769bea..8ff18eaf46ec 100644 --- a/packages/mui/src/hooks/useDataGrid/index.ts +++ b/packages/mui/src/hooks/useDataGrid/index.ts @@ -5,6 +5,9 @@ import { Pagination, pickNotDeprecated, Prettify, + useForm, + UseFormProps, + UseFormReturnType, useLiveMode, useTable as useTableCore, useTableProps as useTablePropsCore, @@ -45,6 +48,9 @@ type DataGridPropsType = Required< | "disableRowSelectionOnClick" | "onStateChange" | "paginationMode" + | "processRowUpdate" + | "onCellEditStart" + | "onRowEditStart" > > & Pick< @@ -52,42 +58,55 @@ type DataGridPropsType = Required< "paginationModel" | "onPaginationModelChange" | "filterModel" >; -export type UseDataGridProps = - Omit< - useTablePropsCore, - "pagination" | "filters" - > & { - onSearch?: (data: TSearchVariables) => CrudFilters | Promise; - pagination?: Prettify< - Omit & { - /** - * Initial number of items per page - * @default 25 - */ - pageSize?: number; - } - >; - filters?: Prettify< - Omit< - NonNullable["filters"]>, - "defaultBehavior" - > & { - /** - * Default behavior of the `setFilters` function - * @default "replace" - */ - defaultBehavior?: "replace" | "merge"; - } - >; - }; +export type UseDataGridProps< + TQueryFnData extends BaseRecord, + TError extends HttpError, + TSearchVariables, + TData, +> = Omit< + useTablePropsCore, + "pagination" | "filters" +> & { + onSearch?: (data: TSearchVariables) => CrudFilters | Promise; + pagination?: Prettify< + Omit & { + /** + * Initial number of items per page + * @default 25 + */ + pageSize?: number; + } + >; + filters?: Prettify< + Omit< + NonNullable["filters"]>, + "defaultBehavior" + > & { + /** + * Default behavior of the `setFilters` function + * @default "replace" + */ + defaultBehavior?: "replace" | "merge"; + } + >; + formProps?: Omit< + UseFormProps, + "autoSave" | "action" | "redirect" + >; +}; export type UseDataGridReturnType< - TData extends BaseRecord = BaseRecord, + TQueryFnData extends BaseRecord = BaseRecord, TError extends HttpError = HttpError, TSearchVariables = unknown, + TData extends BaseRecord = TQueryFnData, > = useTableReturnTypeCore & { dataGridProps: DataGridPropsType; search: (value: TSearchVariables) => Promise; + formProps: Pick< + UseFormReturnType, + "onFinish" | "setId" | "id" | "formLoading" + >; }; /** @@ -134,12 +153,13 @@ export function useDataGrid< metaData, dataProviderName, overtimeOptions, + formProps, }: UseDataGridProps< TQueryFnData, TError, TSearchVariables, TData -> = {}): UseDataGridReturnType { +> = {}): UseDataGridReturnType { const theme = useTheme(); const liveMode = useLiveMode(liveModeFromProp); @@ -263,6 +283,16 @@ export function useDataGrid< }; }; + const { setId, onFinish, id, formLoading } = useForm< + TQueryFnData, + TError, + TData + >({ + ...formProps, + action: "edit", + redirect: false, + }); + return { tableQueryResult, dataGridProps: { @@ -310,6 +340,16 @@ export function useDataGrid< )}`, }, }, + processRowUpdate: async (newRow, oldRow) => { + try { + await onFinish(newRow); + return newRow; + } catch { + return oldRow; + } + }, + onCellEditStart: (params) => setId(params?.id), + onRowEditStart: (params) => setId(params?.id), }, current, setCurrent, @@ -325,5 +365,11 @@ export function useDataGrid< search, createLinkForSyncWithLocation, overtime, + formProps: { + onFinish, + setId, + id, + formLoading, + }, }; } From 1fbce4ebc10ed45ee4fbfd990220b2c330f87116 Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Sun, 26 May 2024 12:28:32 +0300 Subject: [PATCH 02/18] test(useDataGrid): enhance tests with advanced filtering, pagination, and row update scenarios --- .../mui/src/hooks/useDataGrid/index.spec.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/packages/mui/src/hooks/useDataGrid/index.spec.ts b/packages/mui/src/hooks/useDataGrid/index.spec.ts index fbff01c09943..1da7047f0641 100644 --- a/packages/mui/src/hooks/useDataGrid/index.spec.ts +++ b/packages/mui/src/hooks/useDataGrid/index.spec.ts @@ -5,6 +5,7 @@ import { MockJSONServer, TestWrapper } from "@test"; import { useDataGrid } from "./"; import { CrudFilters } from "@refinedev/core"; import { act } from "react-dom/test-utils"; +import { posts } from "@test/dataMocks"; describe("useDataGrid Hook", () => { it("controlled filtering with 'onSubmit' and 'onSearch'", async () => { @@ -198,4 +199,78 @@ describe("useDataGrid Hook", () => { expect(result.current.overtime.elapsedTime).toBeUndefined(); }); }); + + it("when processRowUpdate is called, update data", async () => { + let postToUpdate: any = posts[0]; + + const { result } = renderHook( + () => + useDataGrid({ + resource: "posts", + formProps: { + resource: "posts", + }, + }), + { + wrapper: TestWrapper({ + dataProvider: { + ...MockJSONServer, + update: async (data) => { + const resolvedData = await Promise.resolve({ data }); + postToUpdate = resolvedData.data.variables; + }, + }, + }), + }, + ); + + const newPost = { + ...postToUpdate, + title: "New title", + }; + + await act(async () => { + await result.current.dataGridProps.processRowUpdate( + newPost, + postToUpdate, + ); + }); + + expect(newPost).toEqual(postToUpdate); + }); + + it("when update fails, return old data to row", async () => { + const { result } = renderHook( + () => + useDataGrid({ + resource: "posts", + formProps: { + resource: "posts", + }, + }), + { + wrapper: TestWrapper({ + dataProvider: { + ...MockJSONServer, + update: () => Promise.reject(), + }, + }), + }, + ); + + const oldPost = posts[0]; + + const newPost = { + ...oldPost, + title: "New title", + }; + + await act(async () => { + const returnValue = await result.current.dataGridProps.processRowUpdate( + newPost, + oldPost, + ); + expect(returnValue).toEqual(oldPost); + }); + }); }); From 2286c302313c47ae7fa6c3793e5219434a410023 Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Sun, 26 May 2024 12:29:40 +0300 Subject: [PATCH 03/18] feat(PostList): enable title column editing in data grid --- .../src/pages/posts/list.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx b/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx index c73d76ce2dc4..d58c69e8461d 100644 --- a/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx +++ b/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx @@ -45,7 +45,13 @@ export const PostList: React.FC = () => { type: "number", width: 50, }, - { field: "title", headerName: "Title", minWidth: 400, flex: 1 }, + { + field: "title", + headerName: "Title", + minWidth: 400, + flex: 1, + editable: true, + }, { field: "category.id", headerName: "Category", From a81a401a18b7baaa5f3fd78badf22b3df1531e04 Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Sun, 26 May 2024 12:30:51 +0300 Subject: [PATCH 04/18] feat(cypress-tests): add tests for cell update functionality in data grid --- .../cypress/e2e/all.cy.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/examples/table-material-ui-use-data-grid/cypress/e2e/all.cy.ts b/examples/table-material-ui-use-data-grid/cypress/e2e/all.cy.ts index a34bd49aa939..fa21ddb5d93a 100644 --- a/examples/table-material-ui-use-data-grid/cypress/e2e/all.cy.ts +++ b/examples/table-material-ui-use-data-grid/cypress/e2e/all.cy.ts @@ -171,4 +171,52 @@ describe("table-material-ui-use-data-grid", () => { cy.url().should("include", "current=1"); }); + + it("should update a cell", () => { + cy.getMaterialUILoadingCircular().should("not.exist"); + + cy.intercept("/posts/*").as("patchRequest"); + + cy.getMaterialUIColumnHeader(1).click(); + + cy.get(".MuiDataGrid-cell").eq(1).dblclick(); + + cy.get( + ".MuiDataGrid-cell--editing > .MuiInputBase-root > .MuiInputBase-input", + ) + .clear() + .type("Lorem ipsum refine!") + .type("{enter}"); + + cy.wait("@patchRequest"); + + cy.get(".MuiDataGrid-cell").eq(1).should("contain", "Lorem ipsum refine!"); + }); + + it("should not update a cell", () => { + cy.getMaterialUILoadingCircular().should("not.exist"); + + cy.intercept("PATCH", "/posts/*", (request) => { + request.reply({ + statusCode: 500, + }); + }).as("patchRequest"); + + cy.getMaterialUIColumnHeader(1).click(); + + cy.get(".MuiDataGrid-cell").eq(1).dblclick(); + + cy.get( + ".MuiDataGrid-cell--editing > .MuiInputBase-root > .MuiInputBase-input", + ) + .clear() + .type("Lorem ipsum fail!") + .type("{enter}"); + + cy.wait("@patchRequest"); + + cy.get(".MuiDataGrid-cell") + .eq(1) + .should("not.contain", "Lorem ipsum fail!"); + }); }); From 438e41532782121402dba20ecdbd6b85de0957a6 Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Sun, 26 May 2024 12:32:50 +0300 Subject: [PATCH 05/18] docs(useDataGrid): update properties and add editing section --- .../material-ui/hooks/use-data-grid/index.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md b/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md index b521faca3cc9..8a8381a274c7 100644 --- a/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md +++ b/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md @@ -270,6 +270,40 @@ const MyComponent = () => { When the `useDataGrid` hook is mounted, it will call the `subscribe` method from the `liveProvider` with some parameters such as `channel`, `resource` etc. It is useful when you want to subscribe to live updates. +## Editing + +The hook can further extend editing provided in [``](https://mui.com/x/react-data-grid/editing/) component. To enable column editing, set `editable: "true"` on specific column definition. + +`useDataGrid` will utilize `processRowUpdate` from `` component, which in turn will call [`useForm`](https://refine.dev/docs/data/hooks/use-form/) to attempt updating the row with the new value. + +```tsx +const columns = React.useMemo[]>( + () => [ + { + field: "title", + headerName: "Title", + minWidth: 400, + flex: 1, + editable: true, + }, + ], + [], +); +``` + +Properties from [`useForm`](https://refine.dev/docs/data/hooks/use-form/), with exception of `autoSave`, `action` and `redirect`, are available in `useDataGrid`. + +The hook returns `onFinish`, `setId`, `id` and `formLoading` from `useForm`, which can be used to extend functionality. + +```tsx +const { + dataGridProps, + formProps: { onFinish, setId, id, formLoading }, +} = useDataGrid(); +``` + +By default, the `onCellEditStart` and `onRowEditStart` callbacks from `` will set the `id` on the form, and `processRowUpdate` will call `onFinish`. + ## Properties ### resource From 9d7b49ca5dab9ccd19b0f8dff9d59e3c37b4918b Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Sun, 26 May 2024 12:35:56 +0300 Subject: [PATCH 06/18] chore: create changeset for editable MUI Data Grid feature --- .changeset/honest-beds-rhyme.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/honest-beds-rhyme.md diff --git a/.changeset/honest-beds-rhyme.md b/.changeset/honest-beds-rhyme.md new file mode 100644 index 000000000000..c799643dcf2d --- /dev/null +++ b/.changeset/honest-beds-rhyme.md @@ -0,0 +1,10 @@ +--- +"@refinedev/mui": minor +--- + +feat: editable feature for MUI Data Grid #5656 + +It is now possible to make MUI Data Grid editable by +setting editable property on specific column. + +Resolves #5656 From 7fe92ff89d5bd40b957aed9dfe81c0d5ef91e008 Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Sat, 1 Jun 2024 20:36:03 +0300 Subject: [PATCH 07/18] feat(useDataGrid): replace useForm with useUpdate for enhanced state management --- .../mui/src/hooks/useDataGrid/index.spec.ts | 4 +- packages/mui/src/hooks/useDataGrid/index.ts | 48 ++++++++++++------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/packages/mui/src/hooks/useDataGrid/index.spec.ts b/packages/mui/src/hooks/useDataGrid/index.spec.ts index 1da7047f0641..d3a113296c9a 100644 --- a/packages/mui/src/hooks/useDataGrid/index.spec.ts +++ b/packages/mui/src/hooks/useDataGrid/index.spec.ts @@ -239,7 +239,7 @@ describe("useDataGrid Hook", () => { expect(newPost).toEqual(postToUpdate); }); - it("when update fails, return old data to row", async () => { + /*it("when update fails, return old data to row", async () => { const { result } = renderHook( () => useDataGrid({ @@ -272,5 +272,5 @@ describe("useDataGrid Hook", () => { ); expect(returnValue).toEqual(oldPost); }); - }); + });*/ }); diff --git a/packages/mui/src/hooks/useDataGrid/index.ts b/packages/mui/src/hooks/useDataGrid/index.ts index 8ff18eaf46ec..ba03e751ab16 100644 --- a/packages/mui/src/hooks/useDataGrid/index.ts +++ b/packages/mui/src/hooks/useDataGrid/index.ts @@ -12,6 +12,7 @@ import { useTable as useTableCore, useTableProps as useTablePropsCore, useTableReturnType as useTableReturnTypeCore, + useUpdate, } from "@refinedev/core"; import { useState } from "react"; @@ -49,8 +50,6 @@ type DataGridPropsType = Required< | "onStateChange" | "paginationMode" | "processRowUpdate" - | "onCellEditStart" - | "onRowEditStart" > > & Pick< @@ -96,17 +95,16 @@ export type UseDataGridProps< }; export type UseDataGridReturnType< - TQueryFnData extends BaseRecord = BaseRecord, + TData extends BaseRecord = BaseRecord, TError extends HttpError = HttpError, TSearchVariables = unknown, - TData extends BaseRecord = TQueryFnData, > = useTableReturnTypeCore & { dataGridProps: DataGridPropsType; search: (value: TSearchVariables) => Promise; - formProps: Pick< - UseFormReturnType, - "onFinish" | "setId" | "id" | "formLoading" - >; + formProps: { + processRowUpdate: (newRow: TData, oldRow: TData) => Promise; + formLoading: boolean; + }; }; /** @@ -159,7 +157,7 @@ export function useDataGrid< TError, TSearchVariables, TData -> = {}): UseDataGridReturnType { +> = {}): UseDataGridReturnType { const theme = useTheme(); const liveMode = useLiveMode(liveModeFromProp); @@ -283,7 +281,7 @@ export function useDataGrid< }; }; - const { setId, onFinish, id, formLoading } = useForm< + /*const { setId, onFinish, id, formLoading } = useForm< TQueryFnData, TError, TData @@ -291,7 +289,22 @@ export function useDataGrid< ...formProps, action: "edit", redirect: false, - }); + });*/ + + const { mutate, isLoading: formLoading } = useUpdate(); + + const processRowUpdate = async (newRow: TData, oldRow: TData) => { + try { + await mutate({ + resource: resourceFromProp as string, + id: newRow.id as string, + values: newRow, + }); + return newRow; + } catch (error) { + return oldRow; + } + }; return { tableQueryResult, @@ -340,16 +353,15 @@ export function useDataGrid< )}`, }, }, - processRowUpdate: async (newRow, oldRow) => { + /*processRowUpdate: async (newRow, oldRow) => { try { await onFinish(newRow); return newRow; } catch { return oldRow; } - }, - onCellEditStart: (params) => setId(params?.id), - onRowEditStart: (params) => setId(params?.id), + },*/ + processRowUpdate, }, current, setCurrent, @@ -366,10 +378,14 @@ export function useDataGrid< createLinkForSyncWithLocation, overtime, formProps: { + processRowUpdate, + formLoading, + }, + /*formProps: { onFinish, setId, id, formLoading, - }, + },*/ }; } From 463c09e753f21516c30181518f2b67e26618327b Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Sat, 1 Jun 2024 22:45:14 +0300 Subject: [PATCH 08/18] refactor(useDataGrid): enhance processRowUpdate with explicit promise handling --- .../mui/src/hooks/useDataGrid/index.spec.ts | 4 +- packages/mui/src/hooks/useDataGrid/index.ts | 46 +++++++------------ 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/packages/mui/src/hooks/useDataGrid/index.spec.ts b/packages/mui/src/hooks/useDataGrid/index.spec.ts index d3a113296c9a..1da7047f0641 100644 --- a/packages/mui/src/hooks/useDataGrid/index.spec.ts +++ b/packages/mui/src/hooks/useDataGrid/index.spec.ts @@ -239,7 +239,7 @@ describe("useDataGrid Hook", () => { expect(newPost).toEqual(postToUpdate); }); - /*it("when update fails, return old data to row", async () => { + it("when update fails, return old data to row", async () => { const { result } = renderHook( () => useDataGrid({ @@ -272,5 +272,5 @@ describe("useDataGrid Hook", () => { ); expect(returnValue).toEqual(oldPost); }); - });*/ + }); }); diff --git a/packages/mui/src/hooks/useDataGrid/index.ts b/packages/mui/src/hooks/useDataGrid/index.ts index ba03e751ab16..cd76a035557c 100644 --- a/packages/mui/src/hooks/useDataGrid/index.ts +++ b/packages/mui/src/hooks/useDataGrid/index.ts @@ -5,9 +5,7 @@ import { Pagination, pickNotDeprecated, Prettify, - useForm, UseFormProps, - UseFormReturnType, useLiveMode, useTable as useTableCore, useTableProps as useTablePropsCore, @@ -281,24 +279,26 @@ export function useDataGrid< }; }; - /*const { setId, onFinish, id, formLoading } = useForm< - TQueryFnData, - TError, - TData - >({ - ...formProps, - action: "edit", - redirect: false, - });*/ - const { mutate, isLoading: formLoading } = useUpdate(); const processRowUpdate = async (newRow: TData, oldRow: TData) => { try { - await mutate({ - resource: resourceFromProp as string, - id: newRow.id as string, - values: newRow, + await new Promise((resolve, reject) => { + mutate( + { + resource: resourceFromProp as string, + id: newRow.id as string, + values: newRow, + }, + { + onError: (error) => { + reject(error); + }, + onSuccess: (data) => { + resolve(data); + }, + }, + ); }); return newRow; } catch (error) { @@ -353,14 +353,6 @@ export function useDataGrid< )}`, }, }, - /*processRowUpdate: async (newRow, oldRow) => { - try { - await onFinish(newRow); - return newRow; - } catch { - return oldRow; - } - },*/ processRowUpdate, }, current, @@ -381,11 +373,5 @@ export function useDataGrid< processRowUpdate, formLoading, }, - /*formProps: { - onFinish, - setId, - id, - formLoading, - },*/ }; } From aa2c50548158106e80cc4ed4d9b63ec8830bb35a Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Sat, 1 Jun 2024 22:47:56 +0300 Subject: [PATCH 09/18] docs(useDataGrid): update documentation to reflect useUpdate integration --- .../material-ui/hooks/use-data-grid/index.md | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md b/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md index 8a8381a274c7..74cc9371f482 100644 --- a/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md +++ b/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md @@ -272,9 +272,11 @@ When the `useDataGrid` hook is mounted, it will call the `subscribe` method from ## Editing -The hook can further extend editing provided in [``](https://mui.com/x/react-data-grid/editing/) component. To enable column editing, set `editable: "true"` on specific column definition. +The `useDataGrid` hook extends the editing capabilities provided by the [``](https://mui.com/x/react-data-grid/editing/) component from MUI. To enable column editing, set `editable: "true"` on specific column definitions. -`useDataGrid` will utilize `processRowUpdate` from `` component, which in turn will call [`useForm`](https://refine.dev/docs/data/hooks/use-form/) to attempt updating the row with the new value. +`useDataGrid` leverages [`useUpdate`](https://refine.dev/docs/data/hooks/use-update/) for direct integration with update operations. This change enhances performance and simplifies the interaction model by directly using the update mechanisms provided by Refine. + +Here is how you can define columns to be editable: ```tsx const columns = React.useMemo[]>( @@ -291,18 +293,47 @@ const columns = React.useMemo[]>( ); ``` -Properties from [`useForm`](https://refine.dev/docs/data/hooks/use-form/), with exception of `autoSave`, `action` and `redirect`, are available in `useDataGrid`. +### Handling Updates + +With the integration of useUpdate, processRowUpdate from directly interacts with the backend. This method attempts to update the row with the new values, handling the update logic internally. -The hook returns `onFinish`, `setId`, `id` and `formLoading` from `useForm`, which can be used to extend functionality. +The hook now simplifies the handling of updates by removing the need for managing form state transitions explicitly: ```tsx const { dataGridProps, - formProps: { onFinish, setId, id, formLoading }, + formProps: { processRowUpdate, formLoading }, } = useDataGrid(); ``` -By default, the `onCellEditStart` and `onRowEditStart` callbacks from `` will set the `id` on the form, and `processRowUpdate` will call `onFinish`. +By default, when a cell edit is initiated and completed, the processRowUpdate function will be triggered, which will now use the mutate function from useUpdate to commit changes. + +```tsx +const processRowUpdate = async (newRow: TData, oldRow: TData) => { + try { + await new Promise((resolve, reject) => { + mutate( + { + resource: resourceFromProp as string, + id: newRow.id as string, + values: newRow, + }, + { + onError: (error) => { + reject(error); + }, + onSuccess: (data) => { + resolve(data); + }, + }, + ); + }); + return newRow; + } catch (error) { + return oldRow; + } +}; +``` ## Properties From 29ac18528d9edf6dd4125a820207e898e24def4f Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Sat, 1 Jun 2024 23:10:18 +0300 Subject: [PATCH 10/18] docs(useDataGrid): enhance documentation with links and code clarity --- .../ui-integrations/material-ui/hooks/use-data-grid/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md b/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md index 74cc9371f482..8209096cf6d7 100644 --- a/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md +++ b/documentation/docs/ui-integrations/material-ui/hooks/use-data-grid/index.md @@ -295,7 +295,7 @@ const columns = React.useMemo[]>( ### Handling Updates -With the integration of useUpdate, processRowUpdate from directly interacts with the backend. This method attempts to update the row with the new values, handling the update logic internally. +With the integration of `useUpdate`, processRowUpdate from [``](https://mui.com/x/react-data-grid/editing/) directly interacts with the backend. This method attempts to update the row with the new values, handling the update logic internally. The hook now simplifies the handling of updates by removing the need for managing form state transitions explicitly: From f19498b4038831362cf650e070d995c6bec324de Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Mon, 3 Jun 2024 18:55:22 +0300 Subject: [PATCH 11/18] refactor(refine): streamline useDataGrid hook integration with MUI DataGrid --- .../mui/src/hooks/useDataGrid/index.spec.ts | 50 +------ packages/mui/src/hooks/useDataGrid/index.ts | 126 ++++++++---------- 2 files changed, 64 insertions(+), 112 deletions(-) diff --git a/packages/mui/src/hooks/useDataGrid/index.spec.ts b/packages/mui/src/hooks/useDataGrid/index.spec.ts index 1da7047f0641..a1d83378684e 100644 --- a/packages/mui/src/hooks/useDataGrid/index.spec.ts +++ b/packages/mui/src/hooks/useDataGrid/index.spec.ts @@ -207,9 +207,7 @@ describe("useDataGrid Hook", () => { () => useDataGrid({ resource: "posts", - formProps: { - resource: "posts", - }, + editable: true, }), { wrapper: TestWrapper({ @@ -223,54 +221,20 @@ describe("useDataGrid Hook", () => { }), }, ); - const newPost = { ...postToUpdate, title: "New title", }; await act(async () => { - await result.current.dataGridProps.processRowUpdate( - newPost, - postToUpdate, - ); + if (result.current.dataGridProps.processRowUpdate) { + await result.current.dataGridProps.processRowUpdate( + newPost, + postToUpdate, + ); + } }); expect(newPost).toEqual(postToUpdate); }); - - it("when update fails, return old data to row", async () => { - const { result } = renderHook( - () => - useDataGrid({ - resource: "posts", - formProps: { - resource: "posts", - }, - }), - { - wrapper: TestWrapper({ - dataProvider: { - ...MockJSONServer, - update: () => Promise.reject(), - }, - }), - }, - ); - - const oldPost = posts[0]; - - const newPost = { - ...oldPost, - title: "New title", - }; - - await act(async () => { - const returnValue = await result.current.dataGridProps.processRowUpdate( - newPost, - oldPost, - ); - expect(returnValue).toEqual(oldPost); - }); - }); }); diff --git a/packages/mui/src/hooks/useDataGrid/index.ts b/packages/mui/src/hooks/useDataGrid/index.ts index cd76a035557c..dd509edd9549 100644 --- a/packages/mui/src/hooks/useDataGrid/index.ts +++ b/packages/mui/src/hooks/useDataGrid/index.ts @@ -5,7 +5,6 @@ import { Pagination, pickNotDeprecated, Prettify, - UseFormProps, useLiveMode, useTable as useTableCore, useTableProps as useTablePropsCore, @@ -47,50 +46,48 @@ type DataGridPropsType = Required< | "disableRowSelectionOnClick" | "onStateChange" | "paginationMode" - | "processRowUpdate" > > & Pick< DataGridProps, - "paginationModel" | "onPaginationModelChange" | "filterModel" + | "paginationModel" + | "onPaginationModelChange" + | "filterModel" + | "processRowUpdate" >; -export type UseDataGridProps< - TQueryFnData extends BaseRecord, - TError extends HttpError, - TSearchVariables, - TData, -> = Omit< - useTablePropsCore, - "pagination" | "filters" -> & { - onSearch?: (data: TSearchVariables) => CrudFilters | Promise; - pagination?: Prettify< - Omit & { - /** - * Initial number of items per page - * @default 25 - */ - pageSize?: number; - } - >; - filters?: Prettify< - Omit< - NonNullable["filters"]>, - "defaultBehavior" - > & { - /** - * Default behavior of the `setFilters` function - * @default "replace" - */ - defaultBehavior?: "replace" | "merge"; - } - >; - formProps?: Omit< - UseFormProps, - "autoSave" | "action" | "redirect" - >; -}; +export type UseDataGridProps = + Omit< + useTablePropsCore, + "pagination" | "filters" + > & { + onSearch?: (data: TSearchVariables) => CrudFilters | Promise; + pagination?: Prettify< + Omit & { + /** + * Initial number of items per page + * @default 25 + */ + pageSize?: number; + } + >; + filters?: Prettify< + Omit< + NonNullable["filters"]>, + "defaultBehavior" + > & { + /** + * Default behavior of the `setFilters` function + * @default "replace" + */ + defaultBehavior?: "replace" | "merge"; + } + >; + editable?: boolean; + updateMutationOptions?: { + retry?: number; + }; + }; export type UseDataGridReturnType< TData extends BaseRecord = BaseRecord, @@ -99,10 +96,6 @@ export type UseDataGridReturnType< > = useTableReturnTypeCore & { dataGridProps: DataGridPropsType; search: (value: TSearchVariables) => Promise; - formProps: { - processRowUpdate: (newRow: TData, oldRow: TData) => Promise; - formLoading: boolean; - }; }; /** @@ -149,7 +142,7 @@ export function useDataGrid< metaData, dataProviderName, overtimeOptions, - formProps, + editable = false, }: UseDataGridProps< TQueryFnData, TError, @@ -282,28 +275,27 @@ export function useDataGrid< const { mutate, isLoading: formLoading } = useUpdate(); const processRowUpdate = async (newRow: TData, oldRow: TData) => { - try { - await new Promise((resolve, reject) => { - mutate( - { - resource: resourceFromProp as string, - id: newRow.id as string, - values: newRow, + if (!editable) { + return Promise.resolve(oldRow); + } + + return new Promise((resolve, reject) => { + mutate( + { + resource: resourceFromProp as string, + id: newRow.id as string, + values: newRow, + }, + { + onError: (error) => { + reject(error); }, - { - onError: (error) => { - reject(error); - }, - onSuccess: (data) => { - resolve(data); - }, + onSuccess: (data) => { + resolve(newRow); }, - ); - }); - return newRow; - } catch (error) { - return oldRow; - } + }, + ); + }); }; return { @@ -353,7 +345,7 @@ export function useDataGrid< )}`, }, }, - processRowUpdate, + processRowUpdate: editable ? processRowUpdate : undefined, }, current, setCurrent, @@ -369,9 +361,5 @@ export function useDataGrid< search, createLinkForSyncWithLocation, overtime, - formProps: { - processRowUpdate, - formLoading, - }, }; } From 2a3c94b7c2b708cfb0c2845dcf855d84e2a1da0a Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Fri, 7 Jun 2024 14:03:16 +0300 Subject: [PATCH 12/18] feat(posts): integrate server-side updates in PostList data grid example --- .../src/pages/posts/list.tsx | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx b/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx index d58c69e8461d..6b6daba472c1 100644 --- a/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx +++ b/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx @@ -37,6 +37,33 @@ export const PostList: React.FC = () => { resource: "categories", }); + const handleProcessRowUpdate = React.useCallback( + async (newRow: IPost, oldRow: IPost) => { + try { + const response = await fetch( + `https://api.fake-rest.refine.dev/posts/${newRow.id}`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(newRow), + }, + ); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + return newRow; + } catch (error) { + console.error("Error updating post:", error); + return oldRow; + } + }, + [], + ); + const columns = React.useMemo[]>( () => [ { @@ -94,6 +121,7 @@ export const PostList: React.FC = () => { columns={columns} autoHeight pageSizeOptions={[10, 20, 30, 50, 100]} + processRowUpdate={handleProcessRowUpdate} /> ); From bb921df64dfe2f2926fb6a30af86b21283f16a4b Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Thu, 13 Jun 2024 13:27:59 +0300 Subject: [PATCH 13/18] feat(mui): improve typing for updateMutationOptions and useUpdate in useDataGrid --- packages/mui/src/hooks/useDataGrid/index.ts | 73 +++++++++++---------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/packages/mui/src/hooks/useDataGrid/index.ts b/packages/mui/src/hooks/useDataGrid/index.ts index dd509edd9549..6b670b1293f7 100644 --- a/packages/mui/src/hooks/useDataGrid/index.ts +++ b/packages/mui/src/hooks/useDataGrid/index.ts @@ -30,6 +30,7 @@ import { transformFilterModelToCrudFilters, transformSortModelToCrudSorting, } from "@definitions"; +import { UseUpdateProps } from "@refinedev/core/dist/hooks/data/useUpdate"; type DataGridPropsType = Required< Pick< @@ -56,38 +57,44 @@ type DataGridPropsType = Required< | "processRowUpdate" >; -export type UseDataGridProps = - Omit< - useTablePropsCore, - "pagination" | "filters" - > & { - onSearch?: (data: TSearchVariables) => CrudFilters | Promise; - pagination?: Prettify< - Omit & { - /** - * Initial number of items per page - * @default 25 - */ - pageSize?: number; - } - >; - filters?: Prettify< - Omit< - NonNullable["filters"]>, - "defaultBehavior" - > & { - /** - * Default behavior of the `setFilters` function - * @default "replace" - */ - defaultBehavior?: "replace" | "merge"; - } - >; - editable?: boolean; - updateMutationOptions?: { - retry?: number; - }; - }; +export type UseDataGridProps< + TQueryFnData, + TError extends HttpError, + TSearchVariables, + TData extends BaseRecord, +> = Omit< + useTablePropsCore, + "pagination" | "filters" +> & { + onSearch?: (data: TSearchVariables) => CrudFilters | Promise; + pagination?: Prettify< + Omit & { + /** + * Initial number of items per page + * @default 25 + */ + pageSize?: number; + } + >; + filters?: Prettify< + Omit< + NonNullable["filters"]>, + "defaultBehavior" + > & { + /** + * Default behavior of the `setFilters` function + * @default "replace" + */ + defaultBehavior?: "replace" | "merge"; + } + >; + editable?: boolean; + updateMutationOptions?: UseUpdateProps< + TData, + TError, + TData + >["mutationOptions"]; +}; export type UseDataGridReturnType< TData extends BaseRecord = BaseRecord, @@ -272,7 +279,7 @@ export function useDataGrid< }; }; - const { mutate, isLoading: formLoading } = useUpdate(); + const { mutate } = useUpdate(); const processRowUpdate = async (newRow: TData, oldRow: TData) => { if (!editable) { From 1c3a83c4f8686c063202746bfddfc63eee0949ea Mon Sep 17 00:00:00 2001 From: FXC Intelligence Date: Thu, 13 Jun 2024 13:44:28 +0300 Subject: [PATCH 14/18] fix(mui): correct import of UseUpdateProps to use import type for linter compliance --- packages/mui/src/hooks/useDataGrid/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mui/src/hooks/useDataGrid/index.ts b/packages/mui/src/hooks/useDataGrid/index.ts index 2ab289a86d1a..95d4a13b656b 100644 --- a/packages/mui/src/hooks/useDataGrid/index.ts +++ b/packages/mui/src/hooks/useDataGrid/index.ts @@ -30,7 +30,7 @@ import { transformFilterModelToCrudFilters, transformSortModelToCrudSorting, } from "@definitions"; -import { UseUpdateProps } from "@refinedev/core/dist/hooks/data/useUpdate"; +import type { UseUpdateProps } from "@refinedev/core/dist/hooks/data/useUpdate"; type DataGridPropsType = Required< Pick< From 4ecd9b9acee58071510714be95e69ec94dc0e87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Fri, 14 Jun 2024 15:58:39 +0300 Subject: [PATCH 15/18] chore(editable-data-grid): update example data grid --- .../src/pages/posts/list.tsx | 29 +------------------ 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx b/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx index 9acfcce49c6f..0c743d1c4dd1 100644 --- a/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx +++ b/examples/table-material-ui-use-data-grid/src/pages/posts/list.tsx @@ -14,6 +14,7 @@ export const PostList: React.FC = () => { const { dataGridProps } = useDataGrid({ initialCurrent: 1, initialPageSize: 10, + editable: true, initialSorter: [ { field: "title", @@ -37,33 +38,6 @@ export const PostList: React.FC = () => { resource: "categories", }); - const handleProcessRowUpdate = React.useCallback( - async (newRow: IPost, oldRow: IPost) => { - try { - const response = await fetch( - `https://api.fake-rest.refine.dev/posts/${newRow.id}`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(newRow), - }, - ); - - if (!response.ok) { - throw new Error("Network response was not ok"); - } - - return newRow; - } catch (error) { - console.error("Error updating post:", error); - return oldRow; - } - }, - [], - ); - const columns = React.useMemo[]>( () => [ { @@ -121,7 +95,6 @@ export const PostList: React.FC = () => { columns={columns} autoHeight pageSizeOptions={[10, 20, 30, 50, 100]} - processRowUpdate={handleProcessRowUpdate} /> ); From a3df4e72113e659e501c8b772c38f0e16e6b169e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Fri, 14 Jun 2024 15:58:58 +0300 Subject: [PATCH 16/18] chore(core): export data hook props and return types --- packages/core/src/hooks/data/index.ts | 40 +++++++++++++------ packages/core/src/hooks/data/useUpdateMany.ts | 2 +- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/packages/core/src/hooks/data/index.ts b/packages/core/src/hooks/data/index.ts index 94be2377487f..a6df2ba9040b 100644 --- a/packages/core/src/hooks/data/index.ts +++ b/packages/core/src/hooks/data/index.ts @@ -1,18 +1,34 @@ -export { useList } from "./useList"; -export { useOne } from "./useOne"; -export { useMany } from "./useMany"; +export { useList, UseListProps } from "./useList"; +export { useOne, UseOneProps } from "./useOne"; +export { useMany, UseManyProps } from "./useMany"; -export { useUpdate } from "./useUpdate"; -export { useCreate, UseCreateReturnType } from "./useCreate"; -export { useDelete } from "./useDelete"; +export { useUpdate, UseUpdateProps, UseUpdateReturnType } from "./useUpdate"; +export { useCreate, UseCreateProps, UseCreateReturnType } from "./useCreate"; +export { useDelete, UseDeleteProps, UseDeleteReturnType } from "./useDelete"; -export { useCreateMany, UseCreateManyReturnType } from "./useCreateMany"; -export { useUpdateMany } from "./useUpdateMany"; -export { useDeleteMany } from "./useDeleteMany"; +export { + useCreateMany, + UseCreateManyProps, + UseCreateManyReturnType, +} from "./useCreateMany"; +export { + useUpdateMany, + UseUpdateManyProps, + UseUpdateManyReturnType, +} from "./useUpdateMany"; +export { + useDeleteMany, + UseDeleteManyProps, + UseDeleteManyReturnType, +} from "./useDeleteMany"; export { useApiUrl } from "./useApiUrl"; -export { useCustom } from "./useCustom"; -export { useCustomMutation } from "./useCustomMutation"; +export { useCustom, UseCustomProps } from "./useCustom"; +export { + useCustomMutation, + UseCustomMutationProps, + UseCustomMutationReturnType, +} from "./useCustomMutation"; export { useDataProvider } from "./useDataProvider"; -export { useInfiniteList } from "./useInfiniteList"; +export { useInfiniteList, UseInfiniteListProps } from "./useInfiniteList"; diff --git a/packages/core/src/hooks/data/useUpdateMany.ts b/packages/core/src/hooks/data/useUpdateMany.ts index c2fbbaffa578..d1824217137d 100644 --- a/packages/core/src/hooks/data/useUpdateMany.ts +++ b/packages/core/src/hooks/data/useUpdateMany.ts @@ -134,7 +134,7 @@ type UpdateManyParams = { { ids: BaseKey[]; values: TVariables } >; -type UseUpdateManyReturnType< +export type UseUpdateManyReturnType< TData extends BaseRecord = BaseRecord, TError extends HttpError = HttpError, TVariables = {}, From d7a270e7335afcbe52a13fe28cee91ef7e395f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Fri, 14 Jun 2024 15:59:57 +0300 Subject: [PATCH 17/18] chore: add changeset --- .changeset/orange-seals-brush.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/orange-seals-brush.md diff --git a/.changeset/orange-seals-brush.md b/.changeset/orange-seals-brush.md new file mode 100644 index 000000000000..f451bf63312f --- /dev/null +++ b/.changeset/orange-seals-brush.md @@ -0,0 +1,7 @@ +--- +"@refinedev/core": patch +--- + +chore(core): add missing types of data hooks + +Added missing props and return types of data hooks. From 8cb1394a7da1b0a1f87098c02b8b718e31bf6b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Fri, 14 Jun 2024 16:24:36 +0300 Subject: [PATCH 18/18] fix(use-data-grid): fix editable mutation --- packages/mui/src/hooks/useDataGrid/index.ts | 24 +++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/mui/src/hooks/useDataGrid/index.ts b/packages/mui/src/hooks/useDataGrid/index.ts index 95d4a13b656b..a6c0f561cc06 100644 --- a/packages/mui/src/hooks/useDataGrid/index.ts +++ b/packages/mui/src/hooks/useDataGrid/index.ts @@ -1,15 +1,17 @@ import { + useUpdate, + useLiveMode, + pickNotDeprecated, + useTable as useTableCore, type BaseRecord, type CrudFilters, type HttpError, type Pagination, - pickNotDeprecated, type Prettify, - useLiveMode, - useTable as useTableCore, + type UseUpdateProps, type useTableProps as useTablePropsCore, type useTableReturnType as useTableReturnTypeCore, - useUpdate, + useResourceParams, } from "@refinedev/core"; import { useState } from "react"; @@ -30,7 +32,6 @@ import { transformFilterModelToCrudFilters, transformSortModelToCrudSorting, } from "@definitions"; -import type { UseUpdateProps } from "@refinedev/core/dist/hooks/data/useUpdate"; type DataGridPropsType = Required< Pick< @@ -150,6 +151,7 @@ export function useDataGrid< dataProviderName, overtimeOptions, editable = false, + updateMutationOptions, }: UseDataGridProps< TQueryFnData, TError, @@ -161,6 +163,8 @@ export function useDataGrid< const [columnsTypes, setColumnsType] = useState>(); + const { identifier } = useResourceParams({ resource: resourceFromProp }); + const { tableQueryResult, current, @@ -279,17 +283,23 @@ export function useDataGrid< }; }; - const { mutate } = useUpdate(); + const { mutate } = useUpdate({ + mutationOptions: updateMutationOptions, + }); const processRowUpdate = async (newRow: TData, oldRow: TData) => { if (!editable) { return Promise.resolve(oldRow); } + if (!identifier) { + return Promise.reject(new Error("Resource is not defined")); + } + return new Promise((resolve, reject) => { mutate( { - resource: resourceFromProp as string, + resource: identifier, id: newRow.id as string, values: newRow, },