Nick
Nick

Reputation: 2948

Formik renders twice on initialization

I have this simple example of Formik where i have a simple input. When i run the page i see in the console that it renders twice. The formik package is exactly the same as the first message. Why it renders twice if there is nothing changed?

 const SignupForm = () => {
  const [data, setData] = useState({
    firstName: "",
    lastName: "",
    email: "",
  });
 

  return (
    <Formik
      initialValues={data}
      enableReinitialize
      validateOnBlur={false}
      validateOnChange={false}
      onSubmit={(values, { setSubmitting }) => {
      }}
    >
      {(formik) => {
        console.log(formik);

        return (
          <form onSubmit={formik.handleSubmit}>
            <label htmlFor="firstName">First Name</label>
            <input
              id="firstName"
              type="text"
              {...formik.getFieldProps("firstName")}
            />
            {formik.touched.firstName && formik.errors.firstName ? (
              <div>{formik.errors.firstName}</div>
            ) : null}
            
          </form>
        );
      }}
    </Formik>
  );
};

Upvotes: 0

Views: 696

Answers (1)

Sergey Sosunov
Sergey Sosunov

Reputation: 4600

That is happening due to enableReinitialize property.

Formik itself has a few useEffects inside of it, and a formikReducer. So when you pass enableReinitialize - formikReducer is called 2 times:

payload: {}, type: "SET_ERRORS"
payload: {}, type: "SET_TOUCHED"

Which is happening due to folowing useEffects inside of the source codes:

React.useEffect(function () {
  if (enableReinitialize && isMounted.current === true && !isEqual(initialErrors.current, props.initialErrors)) {
    initialErrors.current = props.initialErrors || emptyErrors;
    dispatch({
      type: 'SET_ERRORS',
      payload: props.initialErrors || emptyErrors
    });
  }
}, [enableReinitialize, props.initialErrors]);
React.useEffect(function () {
  if (enableReinitialize && isMounted.current === true && !isEqual(initialTouched.current, props.initialTouched)) {
    initialTouched.current = props.initialTouched || emptyTouched;
    dispatch({
      type: 'SET_TOUCHED',
      payload: props.initialTouched || emptyTouched
    });
  }
}, [enableReinitialize, props.initialTouched]);

And those ifs are passed due to the initialTouched and initialErrors are initialized inside of the Formik with this:

var initialErrors = React.useRef(props.initialErrors || emptyErrors);
var initialTouched = React.useRef(props.initialTouched || emptyTouched);

So initial values are equal to empty ones which are {}. But inside of the if they have !isEqual(initialErrors.current, props.initialErrors)) for example, so comparison between {} and undefined is passed and we are going inside of the if body and updating the Formik internal state. That is what is causing an additional rerender.

So if you pass the following props to Formik component - console.log will be executed only once

initialErrors={{}}
initialTouched={{}}

Now about how to collect that information:

  1. Configure local minimal workspace with React and Formik
  2. Go to node_modules/formik/dist/formik.cjs.development.js and inject some logging code inside of the formikReducer, simple console.log
  3. In the component that is using <Formik> import it from modified development js file. import { Formik } from "formik/dist/formik.cjs.development";

Formik version: 2.2.9

Upvotes: 2

Related Questions