jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
33.98k stars 2.79k forks source link

validateOnMount not working when enableReinitialize is true #3051

Open yuribit opened 3 years ago

yuribit commented 3 years ago

Bug report

Current Behavior

The form is correctly validated on mount but then when the form gets reinitialized because initialValues have changed, validation is not re-triggered.

Expected behavior

Validation should happen on mount and when the form is reinitialized.

Reproducible example

See Formik's errors and isValid before and after the onBlur event on the input field. https://codesandbox.io/s/formik-codesandbox-template-forked-0e7mx?file=/index.js

Suggested solution(s)

Additional context

When initialValues changes, resetForm() is being called because enableReinitialize is true. Then validateFormWithHighPriority is called because validateOnMount is true but the SET_ERRORS action is not dispatched because initialErrors.current and props.initialErrors are the same.

Your environment

Software Version(s)
Formik 2.2.6
React 17.0.1
Yup 0.32.9
yuribit commented 3 years ago

I believe this issue has been introduced in https://github.com/formium/formik/pull/2625.

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

ghost commented 3 years ago

Any workaround?

ghost commented 3 years ago

Here's my workaround:


    const [initialValues, setInitialValues] = React.useState(defaultValues);
    const frm = useFormik({
      enableReinitialize: true,
      initialValues,
      validationSchema,
      async onSubmit(values) {/* snip */},
    });
    useOnMountAsync(async state => {
      const values = await getFormValues(uid);
      if (!state.mounted) return;
      setInitialValues(() => {
        // HACK: Format immediately after the values are set, after this function returns.
        requestAnimationFrame(() => {
          frm.validateForm();
        });
        return values;
      });
    });

Code for useOnMountAsync so this is not confusing to readers:


/**
 * Hook to use on mount if you need data directly from an action. If your action
 * is async, but you get the redux state data from `useSelector` you can just
 * call your action without `await`ing it, in `useOnMount`.
 * @param {(state:{mounted:boolean}) => void} handler
 * @example
 * useOnMountAsync(async (state) => {
 *   const data = await someAction();
 *   if (!state.mounted) return;
 *   setSomeLocalState(data); // <-- Would error if component no longer mounted.
 * });
 */
export function useOnMountAsync(handler) {
  function useOnMountAsyncWorker() {
    const state = { mounted: true };
    handler(state);
    return () => (state.mounted = false);
  }
  return React.useEffect(useOnMountAsyncWorker, []);
}
github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

github-actions[bot] commented 3 years ago

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 60 days

artyom-88 commented 3 years ago

Got similar problem with enableReinitialize: true and initialValues change. isInitialValid can help, but it's deprecated I use useFormik from 2.2.9 version.

jtan80813 commented 2 years ago

@jaredpalmer

jtan80813 commented 2 years ago

Any updates on this?

jtan80813 commented 2 years ago

@waynebloss. Can you post the complete code on your workaround? where did you get your getFormValues. Thank you

ghost commented 2 years ago

@jtan80813 The getFormValues function can be any function that returns the initial form values. For instance, if you need to load the initial form values from your server, getFormValues would be the function that you create to do that. Here's an example:

async function getFormValues() {
  return fetch('/movies').then(response => response.json());
}