From 690cf4653fdab14ae30c658402ee053540ccef53 Mon Sep 17 00:00:00 2001
From: Jean-Baptiste Kaiser <jb@marmelab.com>
Date: Mon, 22 May 2023 11:11:10 +0200
Subject: [PATCH] Fix create and edit controller's `save` callback should use
 calltime `meta` param

---
 .../create/useCreateController.spec.tsx       | 31 ++++++++++++++++
 .../controller/create/useCreateController.ts  |  3 +-
 .../edit/useEditController.spec.tsx           | 35 +++++++++++++++++++
 .../src/controller/edit/useEditController.ts  |  3 +-
 4 files changed, 70 insertions(+), 2 deletions(-)

diff --git a/packages/ra-core/src/controller/create/useCreateController.spec.tsx b/packages/ra-core/src/controller/create/useCreateController.spec.tsx
index a0083204814..f1c1cf71e05 100644
--- a/packages/ra-core/src/controller/create/useCreateController.spec.tsx
+++ b/packages/ra-core/src/controller/create/useCreateController.spec.tsx
@@ -324,6 +324,37 @@ describe('useCreateController', () => {
         });
     });
 
+    it('should accept meta as a save option', async () => {
+        let saveCallback;
+        const create = jest
+            .fn()
+            .mockImplementationOnce((_, { data }) =>
+                Promise.resolve({ data: { id: 123, ...data } })
+            );
+        const dataProvider = testDataProvider({
+            getOne: () => Promise.resolve({ data: { id: 12 } } as any),
+            create,
+        });
+
+        render(
+            <CoreAdminContext dataProvider={dataProvider}>
+                <CreateController {...defaultProps}>
+                    {({ save }) => {
+                        saveCallback = save;
+                        return null;
+                    }}
+                </CreateController>
+            </CoreAdminContext>
+        );
+        await act(async () =>
+            saveCallback({ foo: 'bar' }, { meta: { lorem: 'ipsum' } })
+        );
+        expect(create).toHaveBeenCalledWith('posts', {
+            data: { foo: 'bar' },
+            meta: { lorem: 'ipsum' },
+        });
+    });
+
     it('should allow the save onError option to override the failure side effects override', async () => {
         jest.spyOn(console, 'error').mockImplementation(() => {});
         let saveCallback;
diff --git a/packages/ra-core/src/controller/create/useCreateController.ts b/packages/ra-core/src/controller/create/useCreateController.ts
index 9fa02541c0b..363a385a6e1 100644
--- a/packages/ra-core/src/controller/create/useCreateController.ts
+++ b/packages/ra-core/src/controller/create/useCreateController.ts
@@ -87,6 +87,7 @@ export const useCreateController = <
                 onSuccess: onSuccessFromSave,
                 onError: onErrorFromSave,
                 transform: transformFromSave,
+                meta: metaFromSave,
             } = {}
         ) =>
             Promise.resolve(
@@ -100,7 +101,7 @@ export const useCreateController = <
                 try {
                     await mutate(
                         resource,
-                        { data, meta },
+                        { data, meta: metaFromSave ?? meta },
                         {
                             onSuccess: async (data, variables, context) => {
                                 if (onSuccessFromSave) {
diff --git a/packages/ra-core/src/controller/edit/useEditController.spec.tsx b/packages/ra-core/src/controller/edit/useEditController.spec.tsx
index 16672f4d17f..ab16d091bf4 100644
--- a/packages/ra-core/src/controller/edit/useEditController.spec.tsx
+++ b/packages/ra-core/src/controller/edit/useEditController.spec.tsx
@@ -618,6 +618,41 @@ describe('useEditController', () => {
         });
     });
 
+    it('should accept meta as a save option', async () => {
+        let saveCallback;
+        const update = jest
+            .fn()
+            .mockImplementationOnce((_, { id, data, previousData }) =>
+                Promise.resolve({ data: { id, ...previousData, ...data } })
+            );
+        const dataProvider = ({
+            getOne: () => Promise.resolve({ data: { id: 12 } }),
+            update,
+        } as unknown) as DataProvider;
+
+        render(
+            <CoreAdminContext dataProvider={dataProvider}>
+                <EditController {...defaultProps} mutationMode="pessimistic">
+                    {({ save }) => {
+                        saveCallback = save;
+                        return <div />;
+                    }}
+                </EditController>
+            </CoreAdminContext>
+        );
+        await act(async () =>
+            saveCallback({ foo: 'bar' }, { meta: { lorem: 'ipsum' } })
+        );
+        await waitFor(() => {
+            expect(update).toHaveBeenCalledWith('posts', {
+                id: 12,
+                data: { foo: 'bar' },
+                previousData: undefined,
+                meta: { lorem: 'ipsum' },
+            });
+        });
+    });
+
     it('should allow the save onSuccess option to override the success side effects override', async () => {
         let saveCallback;
         const dataProvider = ({
diff --git a/packages/ra-core/src/controller/edit/useEditController.ts b/packages/ra-core/src/controller/edit/useEditController.ts
index d873c62bd41..0242f6ca175 100644
--- a/packages/ra-core/src/controller/edit/useEditController.ts
+++ b/packages/ra-core/src/controller/edit/useEditController.ts
@@ -137,6 +137,7 @@ export const useEditController = <
                 onSuccess: onSuccessFromSave,
                 onError: onErrorFromSave,
                 transform: transformFromSave,
+                meta: metaFromSave,
             } = {}
         ) =>
             Promise.resolve(
@@ -155,7 +156,7 @@ export const useEditController = <
                 try {
                     await mutate(
                         resource,
-                        { id, data, meta: mutationMeta },
+                        { id, data, meta: metaFromSave ?? mutationMeta },
                         {
                             onSuccess: async (data, variables, context) => {
                                 if (onSuccessFromSave) {