formik
formik copied to clipboard
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
andonAccept
synchrounsly when selecting a date.onBlur
is only called when the input itself is blurred. - react-datepicker calls
onChange
andonSelect
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 |
Was trying to track down this issue for hours in my app. Nice find @skoging... hopefully this gets addressed soon.
I have some trouble in my project. This issue blocks me from using Formik. Waiting for resolve.
I am using setTouched() on blur when a button is clicked, which uses setValue() and I am seeing the same issues as described above.
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).
Can confirm this, running setFieldTouched
immediately after setting the value validates the previous state, running it inside a setTimeout
with a 1 second delay works correctly
Related: https://github.com/formium/formik/issues/2403
I am using react-dates with Formik and Yup. I created a work around using react useState() and then setFieldValue() inside useEffect().
CodeSandbox: https://codesandbox.io/s/cool-booth-gergp?file=/src/index.js
I used setTimeout
with 100ms to temporary skip this bug:
form.setFieldValue(name, value)
setTimeout(() => form.setFieldTouched(name, true), 100)
I used setTimeout with 100ms to temporary skip this bug:
You also can use a setTimeout
with a 0 delay since it's going to be executed whenever the call stack is empty and after setting the value.
For those that come across this issue you can also temporarily solve this race condition this way:
setFieldValue(name, value).then(() => {
setFieldTouched(name);
});
@KristijanKanalas setFieldValue doesn't return a promise does it?
@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've been able to reproduce this behaviour, setTimeout was a workaround and @KristijanKanalas comment as well Seems like the touched is running before the value even exists, race condition it seems
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
For those that come across this issue you can also temporarily solve this race condition this way:
setFieldValue(name, value).then(() => { setFieldTouched(name); });
This solution was helpful.
Work arrorud fix
right after setting the filed value, I am validating that filed with will work if you are using some custom input field
formik.setFieldValue("name", val).then((e) => {formik?.validateField("name"));