react-admin icon indicating copy to clipboard operation
react-admin copied to clipboard

Custom save doesn't redirect onSuccess due to warnWhenUnsavedChanges

Open nathan-britten opened this issue 3 years ago • 13 comments

What you were expecting: When I submit the form the data is saved, the user is notified, and then the user is redirected to the specified page

What happened instead: The notification is fired however the user is not redirected

Steps to reproduce:

  1. Navigate to https://codesandbox.io/s/nervous-zeh-bxj6ym?file=/src/posts/PostEdit.tsx:2071-2093
  2. Open a post
  3. Edit the title
  4. Click Custom Save
  5. Notification is fired but you are not redirected.

Then remove warnWhenUnsavedChanges from the tabbed form.

Repeat the steps and you will be redirected.

Related code: https://codesandbox.io/s/nervous-zeh-bxj6ym?file=/src/posts/PostEdit.tsx:2071-2093

Other information:

It appears that warnWhenUnsavedChanges is interfering with the redirection of the page

Environment

  • React-admin version: 4.0.1
  • Last version that did not exhibit the issue (if applicable):
  • React version: 17.0.2
  • Browser: Chrome
  • Stack trace (in case of a JS error):

nathan-britten avatar Apr 29 '22 15:04 nathan-britten

Reproduced, thanks!

slax57 avatar May 02 '22 08:05 slax57

In the case you return a promise, you should handle the side effects when the promise resolves:

update(
    'posts',
    {
        id: values.id,
        data: values,
    },
    {
        returnPromise: true,
        mutationMode: 'pessimistic',
        onError: error => {
            notify((error as Error).message, { type: 'error' });
        },
    }
).then(() => {
    notify('it worked');
    redirect('show', 'posts', values.id);
});

I'm marking this as a documentation issue

djhi avatar May 03 '22 08:05 djhi

In the case you return a promise, you should handle the side effects when the promise resolves:

update(
    'posts',
    {
        id: values.id,
        data: values,
    },
    {
        returnPromise: true,
        mutationMode: 'pessimistic',
        onError: error => {
            notify((error as Error).message, { type: 'error' });
        },
    }
).then(() => {
    notify('it worked');
    redirect('show', 'posts', values.id);
});

I'm marking this as a documentation issue

How this works together with server side validation? https://marmelab.com/react-admin/Validation.html#server-side-validation

If I await update and return errors it does not redirect, if I don't await update it does not show server side errors

Oktay28 avatar Jun 01 '22 12:06 Oktay28

Is there any way to make this work together with server side validation? I would also be very much interested in that. :smile:

FlorianChristie avatar Aug 02 '22 13:08 FlorianChristie

i'm seeing this problem with a create form where i want to redirect to the edit after a custom save and it only works without warnWhenUnsavedChanges.

i am doing

dataProvider.create(resource, {data}).then(()=>{
// sideeffects
})

megantaylor avatar Aug 12 '22 19:08 megantaylor

Is this issue still open?

abhijithp05 avatar Jan 24 '23 04:01 abhijithp05

it should be, i am still having the same issues with warnWhenUnsavedChanges and custom saves

megantaylor avatar Mar 03 '23 18:03 megantaylor

I was experiencing the same issue. I think the redirect navigation is attempted at a point where the form is still blocked for navigating, since it was not yet registered that the form is not dirty anymore. So using patch package I made the following modifications in useEditController and useCreateController.

@@ -151,7 +152,7 @@ export var useEditController = function (props) {
                             messageArgs: { smart_count: 1 },
                             undoable: mutationMode === 'undoable',
                         });
-                        redirect(redirectTo, resource, data.id, data);
+                        setTimeout(() => redirect(redirectTo, resource, data.id, data), 50);
                         return [2 /*return*/];
                     });
                 }); },

archfz avatar Mar 23 '23 23:03 archfz

I have the same with mutationOptions={{ onSuccess }}. An example from the docs (https://marmelab.com/react-admin/Edit.html#mutationoptions) doesn't work if warnWhenUnsavedChanges is on. Only redirecting after render is done in useEffect() makes it works together, ex:

[changed, setChanged] = useState();
const onSuccess = () => {
      // (...)
      setChanged(true);
}
useEffect(() => {
      changed && redirect('/posts');
 }, [changed]);
<Edit mutationOptions={{ onSuccess }} warnWhenUnsavedChanges={true} >
//(...)

PawelSuwinski avatar Apr 04 '23 11:04 PawelSuwinski

EDIT: per https://github.com/marmelab/react-admin/issues/7608#issuecomment-1513742911 it seems like I still haven't figured out 100% of the issue.

Especially the following assumption I made, is wrong:

in pessimistic mode the side-effects are executed before the dataProvider call resolves

So please keep a critical mind when reading my answer below :innocent:


Thought I'd add some clarifications about this issue.

You will run into this issue if both conditions are true:

  • You are inside a Create, or inside an Edit with mutationMode='pessimistic'
  • You set warnWhenUnsavedChanges to true on the form

If you provide neither custom mutationOptions nor custom onSubmit, then you should no longer run into this issue once https://github.com/marmelab/react-admin/pull/8839 has been merged.

If you provide a custom onSuccess side-effect, either via the mutationOptions prop or the onSubmit prop, then you need to move this side-effect into a .then(), or await the result of the create or the update before triggering the side-effects, because in pessimistic mode the side-effects are executed before the dataProvider call resolves.

Regarding the questions about server-side validation, I'm not sure I understand the issue there, but to my understanding you can handle the errors in a catch block:

const update = useUpdate(resource, data, {mutationMode: 'pessimistic', returnPromise: true});

try {
  const result = await update(resource, data)
  console.log(result)
} catch (error) {
  // handle server side errors here
  console.error(error)
} finally {
  console.log('done')
}

Hope this helps!

slax57 avatar Apr 18 '23 09:04 slax57

in pessimistic mode the side-effects are executed before the dataProvider call resolves.

This is counter-intuitive. Are you sure about this one?

fzaninotto avatar Apr 18 '23 20:04 fzaninotto

@fzaninotto I have double-checked, and indeed it appears I was wrong about this!! Hence my PR is probably targeting the wrong culprit, I need to push the analysis further... Thanks for pointing this out!

slax57 avatar Apr 20 '23 15:04 slax57

Now that https://github.com/marmelab/react-admin/pull/8882 has been merged I'm wondering if we can consider this issue fixed as well.

Can s.o. give it a try to check if their issue is resolved or not?

Thanks!

slax57 avatar May 05 '23 16:05 slax57