Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation runs on old value when onChange is called within onBlur #2106

Closed
jamime opened this issue Dec 11, 2019 · 3 comments · Fixed by #2116
Closed

Validation runs on old value when onChange is called within onBlur #2106

jamime opened this issue Dec 11, 2019 · 3 comments · Fixed by #2116

Comments

@jamime
Copy link

jamime commented Dec 11, 2019

🐛 Bug report

Current Behavior

I have a complex component that reformats the user input when they blur the component. When using <Field component={Component}/> the validation runs on the previous value.

Expected behavior

Validation should run on the state after the onBlur.

Reproducible example

https://codesandbox.io/s/formik-codesandbox-template-1ewgq
2019-12-11 11 52 20

Suggested solution(s)

N/A

Additional context

Related #2083, #1977, #2025

Your environment

Software Version(s)
Formik 2.0.7
React 16.12.0
TypeScript N/A
Browser Chrome 78.0.3904.108
npm/Yarn N/A
Operating System MacOS 10.14.6
@jamime
Copy link
Author

jamime commented Dec 11, 2019

This looks like it's the culprit

? validateFormWithLowPriority(state.values)

@jaredpalmer
Copy link
Owner

The solution should be to use setFieldValue and setFieldTouched which let you override validation.

So you should be able to write:

const Component = ({ field, form, ...rest }) => {
  const onBlur = e => {
    e.target.value = "Blurred Value";
    form.setFieldValue(field.name, "Blurred Value", true /* force validation */);
    form.setFieldTouched(field.name, true, false /* force abort validation */);
  };

  return (
    <input
      style={{ border: "2px solid red" }}
      {...field}
      onBlur={onBlur}
      {...rest}
    />
  );
};

However, because setFieldValue requires validateOnChange and shouldValidate to run validation, you can't in this situation.

https://github.com/jaredpalmer/formik/blob/master/packages/formik/src/Formik.tsx#L545

The current workaround would be to keep validateOnChange to true, and then override both onChange and onBlur in your component using setFieldValue (disabling validation in onChange, but keeping it in the onBlur). Not great tbh.

I think a solution IMHO is to alter the shouldValidate default from true, to just be the value of the validateOnChange prop (which defaults to true as well). AFAICT this wouldn't be a breaking change, but would support my code above. This would also make it easier to do this kind of parse/formatOnBlur in your example.

Yet again, not having 2nd argument callback to setState (ie. the old inline cDU) is by far the most annoying parts of hooks. It's the cause of so much pain. It would be so much nicer if setXXXXX resolved after the update or had a callback that would run after the commit, but unfortunately this isn't possible with hooks.

@jaredpalmer jaredpalmer self-assigned this Dec 11, 2019
@jamime
Copy link
Author

jamime commented Dec 11, 2019

Thanks for the reply @jaredpalmer

I think a solution IMHO is to alter the shouldValidate default from true, to just be the value of the validateOnChange prop (which defaults to true as well). AFAICT this wouldn't be a breaking change, but would support my code above.

I don't think that will work for the example that I gave where validateOnChange={false} because
setFieldValue wouldn't trigger validation and setFieldTouched would still be looking at stale data.

Changing it so shouldValidate takes precedence over the validateOnChange prop would help with the workaround that you provided.

// before
return validateOnChange && shouldValidate
        ? validateFormWithLowPriority(setIn(state.values, field, value))
        : Promise.resolve();
// after
const willValidate = shouldValidate === undefined ? validateOnChange : shouldValidate;
return willValidate
        ? validateFormWithLowPriority(setIn(state.values, field, value))
        : Promise.resolve();

Regarding the setState lack of callback. Could we introduce a provider that uses the older setState syntax? e.g. a <StateProvider> that gives us state and dispatch where dispatch calls the reducer and returns a promise that resolves when the setState callback is called.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants