Formik: setFieldError is overwritten when using the validation methods.

Created on 24 Jun 2018  路  7Comments  路  Source: formium/formik

I've been beating my head against the wall on this one. I dug into the formik source and finally figured out what was going on. I'm writing to document what I found. Hopefully it'll help someone else. This also might be a bug. If it is, then sweet. I found a bug.

Problem
The problem was in a custom component I was using the SetFieldValue and SetFieldError. Each of these methods take a field name. SetFieldValue's second parameter is the value, SetFieldError's second parameter is the error message.

setFieldValue('birth', value)
setFieldError('birth', value.error);

There are other elements on the form that are validated with yupjs.

I found when using setFieldError, you can't use the validate or the validateSchema functions defined in the withFormik method configuration. Both the validate and the validateScheme overwrite the error collection.

  /**
   * Run validation against a Yup schema and optionally run a function if successful
   */
  runValidationSchema = (values: FormikValues, onSuccess?: Function) => {
    const { validationSchema } = this.props;
    const schema = isFunction(validationSchema)
      ? validationSchema()
      : validationSchema;
    validateYupSchema(values, schema).then(
      () => {
        this.setState({ errors: {} });
        if (onSuccess) {
          onSuccess();
        }
      },
      (err: any) =>
        this.setState({ errors: yupToFormErrors(err), isSubmitting: false })
    );
  };

It happens in the error function where the state is set: this.setState({ errors: yupToFormErrors(err), isSubmitting: false }) .

For example, if I created a custom control called ImpreciseDateControl and wanted to set an error onBlur like below:

<ImpreciseDateControl
    onChange={value => setFieldValue('birth', value)}
    onBlur={value => {
        setFieldTouched('birth', true);
        setFieldError('birth', value.error);
        }
    }
    label="Birth"
    value={values.birth || {}}
/>

Even though I set the setFieldError for the birth field, it will be wiped out by one of the validation methods.

A possible solution is to pass the error collection into the validate methods, but this would require a change to the Formik code. This way the consumer would have the option to preserve all or selected errors.

Most helpful comment

You cannot use handleChange or handleBlur to get this specific behavior. YOu need to manually handle both blur and change with setFieldValue and and setFieldTouched. You'll need to also pass the third argument (i.e. shouldValidate) to each as false.

```js
handleChangeSpecial = (e) {
setFieldValue(props.name, e.target.value, false);
setFieldError(props.name, 'shoot');
}

handleBlurSpecial = () => {
setFieldTouched(props.name, true, false);
setFieldError(props.name, 'shoot');
}```

YOu can also just pass in a validateProp

All 7 comments

You can tell setFieldValue not to run validation by passing third parameter as false.

Setting the third parameter of setFieldValue to false seems to have no effect. There must be something else in play to make this work.

The setFieldValue workaround was also not working for me. Everytime another field was changed, it would remove the error message applied using setFieldError:

  handleBlur(event: React.FocusEvent<HTMLInputElement>) {
    const { formikProps, onValidatePhoneNumber } = this.props;
    onValidatePhoneNumber(event.target.value).catch(() => {
      formikProps.setFieldError('phoneNumber', 'error message goes here');
    });
  }

  render() {
    const { formikProps } = this.props;

    return (
      <Form onSubmit={formikProps.handleSubmit}>
        <Field
          component={Phone}
          name="phoneNumber"
          type="tel"
          label="Label"
          placeholder="Placeholder"
          onBlur={this.handleBlur}
        />
        <SubmitButton type="submit" isSubmitting={formikProps.isSubmitting}>
          Submit
        </SubmitButton>
      </Form>
    );
  }

However, I managed to find a workaround using the Field validation prop using a debounce (so the validation request doesn't fire every time a user types):

  validation(value: string) {
    const { formikProps, onValidatePhoneNumber } = this.props;
    return onValidatePhoneNumber(value)
      .then(() => {
        formikProps.setFieldError('phoneNumber', null);
        return null;
      })
      .catch(() => {
        formikProps.setFieldError('phoneNumber', 'error message goes here');
        return 'error message goes here';
      });
  }

  debouncedValidation = debounce(this.validation, 500);

  handleValidation(value: string) {
    return this.debouncedValidation(value);
  }

  render() {
    const { formikProps } = this.props;

    return (
      <Form onSubmit={formikProps.handleSubmit}>
        <Field
          component={Phone}
          name="phoneNumber"
          type="tel"
          label="Label"
          placeholder="Placeholder"
          validate={this.handleValidation}
        />
        <SubmitButton type="submit" isSubmitting={formikProps.isSubmitting}>
          Submit
        </SubmitButton>
      </Form>
    );
  }

You cannot use handleChange or handleBlur to get this specific behavior. YOu need to manually handle both blur and change with setFieldValue and and setFieldTouched. You'll need to also pass the third argument (i.e. shouldValidate) to each as false.

```js
handleChangeSpecial = (e) {
setFieldValue(props.name, e.target.value, false);
setFieldError(props.name, 'shoot');
}

handleBlurSpecial = () => {
setFieldTouched(props.name, true, false);
setFieldError(props.name, 'shoot');
}```

YOu can also just pass in a validateProp

@jaredpalmer

 const handleStartDateChange = date => {
 setFieldValue('startDate', date, false);
    if (date !== null && isNaN(Date.parse(date)) === true) {
      setFieldError('startDate', 'Invalid date');
    } 
   else {
      setFieldValue('startDate', date, true);
    }
  };

const handleStartDateBlurChange = date => {    
    setFieldTouched('startDate', true, false);
    if (date !== null && isNaN(Date.parse(date)) === true) {
      setFieldError('startDate', 'Invalid date');
    } 
  };

I am using KeyboardDatePicker of material-ui picker so I want to disable my submit button when user types date in the control. On change event, I am checking whether date is valid or not and then setting the error using setFieldError.

But, I am also facing the same error that error set by the setFieldError is overwritten if I update any other field in the form. I can not write the code on handleSubmit of a form, as I want to disable the button if the date is invalid.

Maybe the acceptable workaound is to move the error validation function from custom component to formik's validate handler, As far as I know.

touched from form is best way, dont' use onBlur....

Was this page helpful?
0 / 5 - 0 ratings

Related issues

outaTiME picture outaTiME  路  3Comments

dearcodes picture dearcodes  路  3Comments

najisawas picture najisawas  路  3Comments

PeerHartmann picture PeerHartmann  路  3Comments

jaredpalmer picture jaredpalmer  路  3Comments