react-hook-form: issue: Submission state no longer reset on handleSubmit error.

Version Number

7.43.1

Codesandbox/Expo snack

https://codesandbox.io/s/submit-form-state-tef0l3?file=/src/App.tsx

Steps to reproduce

The 7.42.0 release introduced a regression by removing a try/catch/finally block in the definition of handleSubmit. The call to _subjects.state.next in the finally block ensured that the form submission state (eg isSubmitting etc) was reset if the onValid handler encountered an error. Starting in 7.42.0, if the onValid handler throws an error, such as from a bad API request, the form incorrectly remains in the submitting state and does not update isSubmitting, submitCount, etc.

To reproduce, take a look at the attached CodeSandbox:

  1. Verify that the text isSubmitting: false, submitCount: 0 appears above the input. This shows the initial form state.
  2. Click the submit button.
  3. You should see the form state text above the input change to isSubmitting: true, submitCount: 0.
  4. Wait for the error to be thrown/promise rejected.
  5. Verify that the submit button remains disabled (because isSubmitting isn’t reset to false).
  6. Verify that the form state text continues to show isSubmitting: true, submitCount: 0.

Expected behaviour

The form state should be updated no matter if the onValid handler throws an error or not. If you fork the CodeSandbox and change the installed version of RHF to 7.41.5, you will see the correct behavior: isSubmitting is updated to false, submitCount incremented, and the submit button no longer disabled.

What browsers are you seeing the problem on?

Firefox, Chrome

Relevant log output

No response

Code of Conduct

  • I agree to follow this project’s Code of Conduct

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 21 (13 by maintainers)

Most upvoted comments

as long as the request is responded is still a successful call (even 400/500)

Yeah, I see what you are saying, but I don’t think a lot of applications are structured to think of any response as a success. In most cases a non-200 API response is a failure and it is more helpful to have the form state reflect that failure.

I would recommend doing the following instead, this gives you much better control instead of RHF swallowing the error and response with submitting successfully or not.

Thanks, that is a clear explanation of the recommended pattern. In my case, I rely on a combination of isSubmitting and isSubmitSuccesful to control various parts of the UI, for example disabling the submit button. With React Query, it has been great to pass the promise to handleSubmit so the the form state is aligned with the mutation, but handle any request errors in the mutation code, not handleSubmit.

I appreciate the responsiveness here and the ongoing maintenance of this library. For now, I can revert to 7.41.5 until I find a better pattern for using RHF with libraries like React Query and can update my code.

@Svish The change we have made has been to subscribe to the mutation state and coalesce the form state and the mutation state (instead of awaiting the promise from mutateAsync in the handleSubmit onValid function). Here’s a simplified example:

const {
  mutate,
  isError: isMutationError,
  isLoading: isMutationLoading,
  isSuccess: isMutationSuccess
} = useMutation(...)

const {
  control,
  register,
  handleSubmit,
  formState: { isSubmitting },
} = useForm(...)

const isFormSubmitting = isSubmitting || isMutationLoading

const handleFormSubmit = handleSubmit(
  (values) => mutate(values),
  () => console.log('invalid values')
)

Basically, just use the isSubmitting from RHF to handle the form submission state before the mutation is called (which is just validation really), and then use the mutation state for everything else. Ignore formState.isSubmitSuccessful as it doesn’t contribute any useful information in this scenario. If you need to reset the form, show a notification, etc when the mutation finishes, do those actions in onSuccess and onError passed to the mutate function or, better yet, useEffect hooks that test isError and isSuccess from the mutation state. You’ll need to use useEffect anyway if the post-submit action would cause your component to unmount. Otherwise, you will very likely run into a state update on unmounted component error.

You could absolutely use the suggested approach from @bluebill1049 above with mutateAsync:

const handleFormSubmit = handleSubmit(
  async (values) => {
    try {
      await mutateAsync(values)
    } catch {
      // Do nothing, error is handled in mutation callbacks.
    }
  },
  () => console.log('invalid values')
)

This approach would allow you to keep using isSubmitting as one boolean for the entire lifecycle of the form submission, but I don’t think the verbosity is worth it. An empty catch statement is deceiving, anyway.

I would recommend doing the following instead, this gives you much better control instead of RHF swallowing the error and response with submitting successfully or not.

onSubmit={handleSubmit(async () => {
  try {
    const response = await fetch()

    if (response.status > 200) {
      setError('root.request', {
        type: 'requestStatusError,
        message: 'your message here',
      })
    }
  } catch (e) {
    setError('root.request', {
      type: 'requestRuntimeError,
      message: 'your message here',
    })
  }
})}

As to the changelog, here it is

Thanks, I did not see that file. I bet that I am not the only one who only looks at https://github.com/react-hook-form/react-hook-form/releases and not the changelog file. For example: https://github.com/react-hook-form/react-hook-form/releases/tag/v7.42.0. The new behavior isn’t mentioned there.

yes, it’s already in the doc

handleSubmit function will not swallow errors that occurred inside your onSubmit callback, so we recommend you to try and catch inside async requests and handle those errors gracefully for your customers.

image

I feel the same way.

It made devs forget to handle their promises lol

Yea, @Moshyfawn I will update the documentation to clarify this. I belive we shouldn’t swallow error in the first place, it was a bad design decision.