formik: Calling setFieldValue in response to a field change is out of sync with validations

🐛 Bug report

Current Behavior

The example im working with is let’s say i have two drop down menus 1 & 2.

When menu1 changes, i want to reset the value of menu2. When I do this, and both fields are required, both menu1 & menu2 are invalid, even though menu1 has a value.

Expected behavior

I am updating the value of menu2 in componentDidUpdate when formik.values.menu1 changes. I feel like calling setFieldValue('menu2,' '') should be safe.

Reproducible example

https://gist.github.com/agmcleod/921c5798c4ba7285bc12bff8ebd6d82b

I tried to use the code sandbox templates, but yup validations werent working there for some reason.

Suggested solution(s)

Your environment

Software Version(s)
Formik 2.0.6
React 16.12.0
Browser Firefox
npm/Yarn yarn 1.13.0
Operating System MacOS Mojave

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 30
  • Comments: 36

Commits related to this issue

Most upvoted comments

I had a similar issue and solved it by calling setFieldTouched in a timeout.

const handleChange = (value: string) => {
  formik.setFieldValue(name, value);
  setTimeout(() => formik.setFieldTouched(name, true));
};

I had a similar issue and solved it by calling setFieldTouched in a timeout.

const handleChange = (value: string) => {
  formik.setFieldValue(name, value);
  setTimeout(() => formik.setFieldTouched(name, true));
};

worked me

Is there any other solution because it is ultimately a problem or not?

Solution without setTimeout is here:

const handleChange = async (value: string) => {
  await formik.setFieldValue(name, value);
  formik.setFieldTouched(name, true)
};

Pay attention that function setFieldValue in formik is an async function(it returns a Promise), therefore, when you want to change x field value as a result of y field , you have to put the calling for setFIeldValue on y field in a callback to the first setFieldValue call: for example:

onChange= (_, value) => { setFIeldValue(x, value).than( (res) =>{ setFieldValue(y, /* your desired value */) } }

(or using async await syntax sugaring)

I was just bitten by this issue and await on setValue helped me. I was actually considering it before, but bc the docs says it’s return value is “void” I didn’t even bother trying it out.

I’m confused on what grounds has this issue been closed. There is no resolution or recommendation attached to closing it. I would expect to at least fix the bug in the docs or ideally provide a recommendation or even a link to the docs where this is explained.

(On a higher level, an API where one has to await for setValue before being able to call setTouched, and if not then there is no exception reported, but the setValue silently does not work, looks brittle at least, if not wrong.)

The gist is not loading for me on my network but I was having a similar issue and figured I’d mention it in case it helps someone else while upgrading…

I had multiple selects using setFieldValue but the validation always seemed to be one step behind. In my change handler I was calling setFieldValue and then setFieldTouched to touch and set the value of the field. Both of these functions trigger the validate function and it looks like the setFieldTouched function was returning last and running using the value of the field before it was updated. This showed the field as invalid even though it had a value.

In short I just updated the setFieldTouched function to not validate the form.

// Set field to touched but do not trigger validation props.form.setFieldTouched(field.name, true, false);

Anyways, for others coming across this, I was able to get it to work with the following:

       const handleMyFieldChange: ChangeEventHandler<
          HTMLInputElement
        > = async (e) => {
          if (e.target.value === 'no') {
            await setFieldValue('my_other_field', DEFAULT_VALUE)
            setFieldTouched('my_other_field', DEFAULT_TOUCHED)
          }
        }

Where DEFAULT_VALUE is some default value I want my fields to clear to when hidden, and DEFAULT_TOUCHED is false.

The await is key here, which the type definition does not include (see PR linked in comment above)

^ it seems the typescript definition for setFieldValue is wrong; it shows return value as void but this fix worked great for me when nothing else did.

I had the same “trouble” and saw this issue here at GitHub today, there is still no update in the docs and types.d.ts-file of the packages, at least what I can see?!

still: setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void; instead of: setFieldValue: (field: string, value: any, shouldValidate?: boolean) => Promise<void>;

I had to overwrite the types in my project manually and then it works fine with async-await - but setFieldTouchedworks as expected as void without `Promise<void>, the potential of causing some issues is high if you come back to your code later, so always need to provide comments with the link to this issue:

setFieldValue={
  setFieldValue as (
    field: string,
    value: unknown,
    shouldValidate?: boolean ,
  ) => Promise<void>
} // setFieldValue is a promise we need to use await - https://github.com/jaredpalmer/formik/issues/2059

Are there any plans to update this - or do I miss something? 😃

I’m still getting this issue at version 2.2.9 Fixed by @sapkra 's approach However, it makes code long and not descriptive. I hope there will be a patch for it soon 😃

onChange={(newValue) => {
    setFieldValue('my-field-name', format(newValue), true); // 3rd param means should validate, but it's not working, I guess
    setTimeout(() => setFieldTouched('my-field-name', true)); // this fixes the issue
}

I had a similar issue and solved it by calling setFieldTouched in a timeout.

const handleChange = (value: string) => {
  formik.setFieldValue(name, value);
  setTimeout(() => formik.setFieldTouched(name, true));
};

worked me

Is there any other solution because it is ultimately a problem or not?

I am having this same issue on Formik latest version is 2.4.5 😿 This should definetely be reopened.

@brandonlenz 's workaround did work for me, but the impression I get is that touched doesn’t really have anything to do with the problem, the fix works simply because it retriggers the validation which should be handled by setValue in the first place. It is the same workaround as calling setValue again on a setTimeout.

@jaredpalmer would you mind re-opening this issue please?

does Formik receive any updates on this topic and in general in a whole codebase? I overcame this problem by doing my own setFieldValueAndValidate function. It would be really awesome if this will be marked as an open issue ✌️

const setFieldValueAndValidate = async (field: string, value: unknown) => {
  await setFieldValue(field, value);
  validateField(field);
};

I am having the same issue using "formik": "^2.4.5" adding setFieldTouched does not work on Safari, it works only in Chrome

setFieldValue("password", '');
setTimeout(() => setFieldTouched("password", true));

I am also facing the same issue with “formik”: “^2.1.4”. any solution for this apart from using a timeout, which seems really hacky.

Shouldn’t the setFieldTouched() be called inside the setFieldValue() as part of default implementation of setFieldValue ?

Just fyi, I’m still getting this on v2.2.9. I solved it using the setTimeout on every setFieldTouched call. I wasn’t able to test using await, as I’m using typescript and the type signature shows it just returning void, not a Promise. 😃

I run into this.

My solution, call setFieldTouched(name) before setFieldValue(name, value, true). Then the validation will work as normal 😃

I had a similar issue and solved it by calling setFieldTouched in a timeout.

const handleChange = (value: string) => {
  formik.setFieldValue(name, value);
  setTimeout(() => formik.setFieldTouched(name, true));
};

worked me Is there any other solution because it is ultimately a problem or not?

Solution without setTimeout is here:

const handleChange = async (value: string) => {
  await formik.setFieldValue(name, value);
  formik.setFieldTouched(name, true)
};

setTimeout solved the issue. thanks!

There are PRs which are changing this type + docs but they are still not merged: https://github.com/formium/formik/pull/2648 https://github.com/formium/formik/pull/2384