formik: setFieldValue to undefined deletes the value from values and prevents errors from showing / touched being updated on submit

šŸ› Bug report

Current Behavior

When setFieldValue() is called to set a field to undefined, Formik deletes it from values. The consequence of this is that Formik no longer marks the field as touched on submit and therefore error messages for the field no longer show (using <ErrorMessage>)

Steps to repro:

  1. Formik state after submit is clicked and the input field is not changed by the user. As you can see the error message is visible and touched is automatically set.

image

  1. Formik state after (a) input field is changed to a value, then (b) the input field is cleared, and then Ā© submit is clicked again. The nuance here is that when the field is cleared, setFieldValue is called to set the form field to undefined. You can see the error exists, but the field name is no longer in values and touched does not have the field name either.

image

Expected behavior

On submit, Formik should continue to mark ALL the keys in initialValues as touched.

Reproducible example

https://codesandbox.io/s/formik-setfieldvalue-deletes-the-value-from-values-and-prevents-errors-from-showing-on-submit-ysxdh?fontsize=14&hidenavigation=1&theme=dark

Suggested solution(s)

Two potential solutions (a) Formik should set the field value to undefined instead of deleting it from values when setFieldValue is called with the value of undefined OR (b) Formik should clone the keys from initial values and use that to determine which fields to touch on submit instead of the current set of keys in values.

Additional context

None

Your environment

Software Version(s)
Formik 2.1.1 (and 1.1.1)
React 16.12.0
TypeScript n/a
Browser Chrome Version 80.0.3987.122 (Official Build) (64-bit)
npm/Yarn Yarn
Operating System Mac OS X

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 37
  • Comments: 15

Most upvoted comments

Still unresolved in 2023?

@zyofeng You’re right, my apologies. I failed to paste the important part, which was a replacement of setIn that I was using that changes the default setIn behavior. I’ve updated my original comment to include it.

I got the same problem. The onChange function set field value to undefined. And then the form shows no error message. Because the errors and values object has delete the field key. 2023-11-02 I am crazy.

also, this is the issue when using initialValues and you don’t use all values, user click ā€œsubmitā€ and we never show error on fields that are emptym because of undefined

For anyone who has run into this issue and needs a workaround, one solution is to create your own wrapper around Formik’s setFieldValue function to override its behavior. You can do this by either wrapping Formik entirely (which is more work but allows for more seamless integration) or just calling a surrogate function manually.

Here is what a surrogate function would look like:

function setFieldValue(formik: Formik<Values>, field: string, value: any, shouldValidate?: boolean): void {
  // Override default behavior by forcing undefined to be set on the state
  if (value === undefined) {
    const values = setIn(formik.state.values, field, undefined);
    formik.setValues(values);

    const willValidate = shouldValidate === undefined ? this.props.validateOnChange : shouldValidate;
    if (willValidate) {
      formik.validateForm(values);
    }
  } else {
    // Use default behavior for normal values
    formik.setFieldValue(field, value, shouldValidate);
  }
}

This doesn’t work as setIn is the one that removes the obj if value is undefined. see line 130

https://github.com/formium/formik/blob/2d613c11a67b1c1f5189e21b8d61a9dd8a2d0a2e/packages/formik/src/utils.ts

For anyone who has run into this issue and needs a workaround, one solution is to create your own wrapper around Formik’s setFieldValue function to override its behavior. You can do this by either wrapping Formik entirely (which is more work but allows for more seamless integration) or just calling a surrogate function manually.

Here is what a surrogate function would look like:

function setFieldValue(formik: Formik<Values>, field: string, value: any, shouldValidate?: boolean): void {
  // Override default behavior by forcing undefined to be set on the state
  if (value === undefined) {
    const values = setIn(formik.state.values, field, undefined);
    formik.setValues(values);

    const willValidate = shouldValidate === undefined ? this.props.validateOnChange : shouldValidate;
    if (willValidate) {
      formik.validateForm(values);
    }
  } else {
    // Use default behavior for normal values
    formik.setFieldValue(field, value, shouldValidate);
  }
}

/**
 * A copy of Formik's setIn function, except it assigns undefined instead of deleting the property if the value
 * being set is undefined.
 * https://github.com/jaredpalmer/formik/issues/2332
 */
function setIn(obj: any, path: string, value: any): any {
  const res: any = _.clone(obj); // This keeps inheritance when obj is a class
  let resVal: any = res;
  let i = 0;
  const pathArray = _.toPath(path);

  for (; i < pathArray.length - 1; i++) {
    const currentPath: string = pathArray[i];
    const currentObj: any = getIn(obj, pathArray.slice(0, i + 1));

    if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) {
      resVal = resVal[currentPath] = _.clone(currentObj);
    } else {
      const nextPath: string = pathArray[i + 1];
      resVal = resVal[currentPath]
        = isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {};
    }
  }

  // Return original object if new value is the same as current
  if ((i === 0 ? obj : resVal)[pathArray[i]] === value) {
    return obj;
  }

  if (value === undefined) {
    resVal[pathArray[i]] = undefined;
  } else {
    resVal[pathArray[i]] = value;
  }

  // If the path array has a single element, the loop did not run.
  // Deleting on `resVal` had no effect in this scenario, so we delete on the result instead.
  if (i === 0 && value === undefined) {
    res[pathArray[i]] = undefined;
  }

  return res;
}

I agree, formik should be setting the value to undefined, not deleting the property from the values object. Many operations in Formik rely on the existence of the entry in the values object in order to know what is errored/touched, and by deleting, it is wiping that data.

These lines seem to be the culprits, although there may be others: https://github.com/jaredpalmer/formik/blob/master/packages/formik/src/utils.ts#L131 https://github.com/jaredpalmer/formik/blob/master/packages/formik/src/utils.ts#L139