Open amir-virta opened 4 years ago
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
Those two lines lead to #781, which attempts to fix #727. The PR title suggests this is intended behaviour for setIn
, but it seems the original issue was only about setFieldError
, where it makes sense for the keys to be removed.
Was it really the intention to have setFieldValue
be affected by this?
Thanks for filing this, I suspected that this "feature" we were leaning on was actually a bug! Fwiw: I created https://github.com/formik/formik/issues/2598 to backfill this functionality.
Guys, any update on this? Seems to me that the issue is still there.
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;
}
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
@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.
This is an annoying issue, I spent an hour figuring out the issue, and it's just because if a value is undefined
, Formik just ignores it... like Formik set error
but touched
is false... also for submit... I must add null or empty strings to initialValues to start showing errors
Using null
or empty strings also worked for me but I think this should be considered just as a workaround since undefined
values shouldn't be removed from values
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
Still unresolved in 2023?
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.
Same problem here, and it took a while to figure out that my issue was this :(
Just ran into this issue myself. Had to work around it by using setValues
:
const handleClear = (name: keyof FormValues) => () => {
/**
* setFieldValue to undefined deletes the value from values
* https://github.com/jaredpalmer/formik/issues/2332
*/
setValues((previous) => ({
...previous,
[name]: undefined,
}));
};
This issue is still there. On clearing the selection, once we try to set the value as undefined, on submit we are not getting the value with the version of "^2.1.5". So, as a work around, currently I'm overlapping the current outcome with the initialValue. Cause, if some value is not coming, I'm expecting that, it will be undefined only.
const initialCustomerFilterValues = {
customerStatus: undefined,
organizationStatus: undefined
}
<Formik
initialValues={initialCustomerFilterValues}
onSubmit={(values) => {
filterHandler({ ...initialCustomerFilterValues, ...values })
}}
>
...
</Formik>
🐛 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.
2) Formik state after (a) input field is changed to a value, then (b) the input field is cleared, and then (c) 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.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 invalues
.Additional context
None
Your environment