formik: Incorrect validation if setFieldTouched is executed immediatly after setFieldValue

🐛 Bug report

Current Behavior

There is an issue with calling setFieldTouched, setTouched, or onBlur immediatly after updating the form values. Due to using state.values here and here, any state change has to be commited (re-rendered) before a call to setFieldTouched, setTouched, or onBlur, otherwise the previous values will be validated instead.

This looks to be the same issue as #106, and is likely a regression that occured during the v2 hooks rewrite.

Expected behavior

Calling setFieldTouched immediatly after setFieldValue should not result in incorrect validation.

Reproducible example

https://codesandbox.io/s/formik-codesandbox-template-htdsu?file=/index.js

  • @material-ui/pickers calls onChange and onAccept synchrounsly when selecting a date. onBlur is only called when the input itself is blurred.
  • react-datepicker calls onChange and onSelect synchrounsly when selecting a date. onBlur is only called when the input itself is blurred.

Suggested solution(s)

Additional context

https://github.com/jaredpalmer/formik/issues/2432 https://github.com/jaredpalmer/formik/issues/106

Fix in v1: https://github.com/jaredpalmer/formik/pull/176

Your environment

Software Version(s)
Formik 2.1.4
React 16.13.1
TypeScript
Browser
npm/Yarn
Operating System

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 30
  • Comments: 16 (1 by maintainers)

Most upvoted comments

For those that come across this issue you can also temporarily solve this race condition this way:

setFieldValue(name, value).then(() => {
    setFieldTouched(name);
});

Getting the same issue when using formik-material-ui-pickers. Found a temporary workaround which seems to work so far - setFieldTouched(fieldName, true, false) in the onAccept callback. The 3rd parameter false makes formik skip validation on that call, so instead it would get the validation result from the earlier setFieldValue call (which, presumably, has the correct values).

As @KristijanKanalas mentioned, setFieldValue will return a promise so his workaround fixes it. Although, for example in case you’d like async/await style:

onChange: async value => {
  await setFieldValue(field, value)
  await setFieldTouched(field, true)
}

They either do actual same result

Was trying to track down this issue for hours in my app. Nice find @skoging… hopefully this gets addressed soon.

@KristijanKanalas setFieldValue doesn’t return a promise does it?

You would think not if you look at the types it’s defined as setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void; but in the actual implementation it does in fact return a promise. A simple console.log confirms it, and that’s why this solution works. To be fair it might be easily broken in the future if they change the implementation so I don’t wholeheartedly recommend this but it is a working solution for now.

I have some trouble in my project. This issue blocks me from using Formik. Waiting for resolve.