jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
33.97k stars 2.79k forks source link

Nested object is converted to an array in touched/errors when onBlur/onChange is run if nested object property is numeric #3419

Open Dylan-Cairns opened 2 years ago

Dylan-Cairns commented 2 years ago

Bug report

When the values object for a form contains a nested object, if one of those object properties (ie a field name in the form) is numeric, then when onBlur or onChange is triggered, the nested object will be replaced with an array. A value will be set for the element of the array at the index matching the integer which is a property name. In the touched object, the value 'true' will be assigned to the element at that index. In the errors object, the error text will be assigned.

For example, if the field name is '3', the nested object in 'touched' will be converted to an otherwise empty array with the value 'true' at index 3.

Current Behavior

As described above. In the above example the shape would look like:

values = {
  someKey: "someValue",
  nestedObject: { 3: "someText" }
}

touched = {
  someKey: "someValue",
  nestedObject: [ undefined, undefined, undefined, true ]
}

Expected behavior

The nested object should remain an object. The object property at the matching key should be updated appropriately based on onBlur/onChange.

In the example above, the correct shape of touched should be

values = {
  someKey: "someValue",
  nestedObject: { 3: "someText" }
}

touched = {
  someKey: "someValue",
  nestedObject: { 3: true }
}

Reproducible example

This sandbox has a form with 3 fields. The first 2 field names are numbers (1 and 5 respectively) and the 3rd is a string.

Code Sandbox

  1. Play with adding and removing text to each of the 3 input fields to observe the effects on the touched object and the errors object as described above.
  2. Note that if valid input is entered into first 2 fields (which have numeric names) and only the 3rd field (with a string name) remains, the nested value will be converted from an array back into an object.

Your environment

Software Version(s)
Formik 2.2.9
React 17.0.2
TypeScript N/A
Browser Firefox & Chrome
npm/Yarn npm 8.1.0 / yarn 1.22.0
Operating System Windows 10
sanduluca commented 2 years ago

any progress here ?

flogaribal commented 2 years ago

Does anybody have a fix or a workaround? Or maybe it has been fixed but not released yet?

Dylan-Cairns commented 2 years ago

For our use case it's very easy to avoid this issue, the solution is don't use numeric keys in nested objects.

The main problem is just knowing this behavior exists.

I'm not sure a code change is really required here, but maybe a note should be added to the docs, perhaps the 'nested objects' section under 'arrays'.

https://formik.org/docs/guides/arrays

flogaribal commented 2 years ago

Thanks for your answer @Dylan-Cairns I agree that should be at least documented but I think, with my little Formik experience, as it is working as expected on values it can/should work perfectly on errors & touched too...

alienzhangyw commented 1 year ago

Any solutions here? It's annoying when I have to use numeric keys.

sanduluca commented 1 year ago

Any solutions here? It's annoying when I have to use numeric keys.

The only solution for now is to add a string prefix to number keys.

values = {
  someKey: "value1",
  nestedObject: { "f3": "value for 3" }
}
alienzhangyw commented 1 year ago

Any solutions here? It's annoying when I have to use numeric keys.

The only solution for now is to add a string prefix to numeric keys.

values = {
  someKey: "value1",
  nestedObject: { "f3": "value for 3" }
}

I find it's OK to use number keys since it make no difference when getting error/touched from formik.errors/touched, you can just use formik.errors.nestedObject[numeric_key] as the same as string keys. And for formik.values, there is also no differece since all keys will be converted to string when posting JSON data to backend.

sanduluca commented 1 year ago

I find it's OK to use number keys since it make no difference when getting error/touched from formik.errors/touched, you can just use formik.errors.nestedObject[numeric_key] as the same as string keys. And for formik.values, there is also no differece since all keys will be converted to string when posting JSON data to backend.

If all those undefined in the array doesn't mess your stuff I agree its fine. Also to mention for small numeric key you dont fell the difference, but lets say the key is 94788, what will be the length of the nestedObject array ? I would say minimum 94788 if it is not the max key. So iterating over big array with undefined to just show to errors looks a little messy.

alienzhangyw commented 1 year ago

If all those undefined in the array doesn't mess your stuff I agree its fine. Also to mention for small numeric key you dont fell the difference, but lets say the key is 94788, what will be the length of the nestedObject array ? I would say minimum 94788 if it is not the max key. So iterating over big array with undefined to just show to errors looks a little messy.

The array is a sparse array, means the 'undefined' keys are actually 'empty', no memory cost, and will be skipped when map or loop.

olivergg commented 3 months ago

If all those undefined in the array doesn't mess your stuff I agree its fine. Also to mention for small numeric key you dont fell the difference, but lets say the key is 94788, what will be the length of the nestedObject array ? I would say minimum 94788 if it is not the max key. So iterating over big array with undefined to just show to errors looks a little messy.

The array is a sparse array, means the 'undefined' keys are actually 'empty', no memory cost, and will be skipped when map or loop.

Are you sure about that? I've just spent hours investigating an "out of memory" bug (visible in the browser console) in a Formik form of a proprietary React application. The bug indeed came from the numeric IDs used as field names, but since these IDs were very high values (> 1E9), this caused the bug described in this ticket. Adding a string prefix to the IDs works well as a solution.

alienzhangyw commented 3 months ago

Are you sure about that? I've just spent hours investigating an "out of memory" bug (visible in the browser console) in a Formik form of a proprietary React application. The bug indeed came from the numeric IDs used as field names, but since these IDs were very high values (> 1E9), this caused the bug described in this ticket. Adding a string prefix to the IDs works well as a solution.

You can confirm by console.log(array) to see if it has empty in output, not null or undefined. image