formik icon indicating copy to clipboard operation
formik copied to clipboard

Incorrect validation if setFieldTouched is executed immediatly after setFieldValue

Open skoging opened this issue 4 years ago • 17 comments

🐛 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

skoging avatar May 05 '20 14:05 skoging

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

mcabs3 avatar May 12 '20 17:05 mcabs3

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

e-bashtan avatar Jun 10 '20 10:06 e-bashtan

I am using setTouched() on blur when a button is clicked, which uses setValue() and I am seeing the same issues as described above.

mattsputnikdigital avatar Jul 06 '20 16:07 mattsputnikdigital

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).

syy1125 avatar Aug 07 '20 17:08 syy1125

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

dsgriffin avatar Aug 24 '20 09:08 dsgriffin

Related: https://github.com/formium/formik/issues/2403

kyle-johnson avatar Oct 01 '20 00:10 kyle-johnson

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

matussovcik avatar Oct 17 '20 16:10 matussovcik

I used setTimeout with 100ms to temporary skip this bug:

 form.setFieldValue(name, value)
setTimeout(() => form.setFieldTouched(name, true), 100)

mogaozq avatar May 05 '21 11:05 mogaozq

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.

aleksanderantropov avatar Jun 17 '22 08:06 aleksanderantropov

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

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

KristijanKanalas avatar Jul 14 '22 12:07 KristijanKanalas

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

vai0 avatar Aug 25 '22 21:08 vai0

@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.

KristijanKanalas avatar Aug 25 '22 22:08 KristijanKanalas

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

LuisCor avatar Mar 23 '23 13:03 LuisCor

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

rikusen0335 avatar Apr 04 '23 09:04 rikusen0335

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.

bravo-stack avatar Feb 20 '24 19:02 bravo-stack

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"));

sachi1402 avatar Apr 08 '24 09:04 sachi1402