Search code examples
reactjsreact-hook-form

Why does `formState.errors` execute multiple times?


I am building a front-end application using React, TypeScript, and React Hook Form for form management.

I decided to run some tests to try to better understand how React Hook Form works, and I ended up encountering a "problem" when using formState.errors.

Basically, after the form is submitted, the component is rendered more than once, regardless of whether formState.errors contains errors or not.

I looked in the documentation, and I couldn't find anything about this, so I don't know if it would be expected behavior, a bug, or a failure in my implementation...

The thing is, it seems incorrect to me that more than one re-render occurs when the form has been submitted only once.

Does anyone know what might be happening?

Notes:

  • I am not using React Strict Mode.
  • I am using React Hook Form version 7.52.1

Links

Demo Gif Sandbox Link

Example code:

import * as React from "react";
import * as ReactDOM from "react-dom";
import { useForm } from "react-hook-form";

type FormInputs = {
  firstName: string;
};

const App = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormInputs>({
    defaultValues: {
      firstName: "",
    },
  });

  function onSubmit() {
    console.log("onSubmit");
  }

  console.log("errors", errors);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>First Name</label>
      <br />
      <input type="text" {...register("firstName", { required: true })} />
      <br />
      <input type="submit" />
    </form>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

I expected the componentto be rendered only once after the form was submitted.


Solution

  • According to the author of React Hook Form, executing handleSubmit should make the component render twice.

    On CodeSandbox, logging the errors object might show four renders, but running the same code locally, it results in only two renders. (This might be due to how CodeSandbox handles updates, you could try change it to console.log("hello world") and see it only prints twice instead of four times)

    To understand what's happening, we can modify the code to print the validation stages, using useEffect:

    const App = () => {
      const {
        register,
        handleSubmit,
        formState: { errors, isSubmitting, isSubmitSuccessful },
      } = useForm<FormInputs>({
        defaultValues: {
          firstName: '',
        },
      });
    
      const onSubmit = (data: FormInputs) => {
        console.log('onSubmit:', data);
      };
    
      useEffect(() => {
        console.log('errors updated:', errors);
      }, [errors]);
    
      useEffect(() => {
        console.log('isSubmitting:', isSubmitting);
      }, [isSubmitting]);
    
      useEffect(() => {
        console.log('isSubmitSuccessful:', isSubmitSuccessful);
      }, [isSubmitSuccessful]);
    
      console.log('App rendered');
    
      return (
        ...
      );
    };
    

    Once we type "test" as input and click submit, we can see the validation stages taking action in the console:

    App rendered
    isSubmitting: true
    onSubmit: {firstName: 'test'}
    App rendered
    errors updated: {}
    App rendered
    isSubmitting: false
    isSubmitSuccessful: true
    

    So basically:

    • First render once the submitting process starts.
    • Second render once validation errors are cleared.
    • Third render once submitting process ends.

    Here you can actually see it even renders three times, because of isSubmitting value changing as well.