From 8ef54b7abbd4826fe1b6a5f0d1e12b34d06fdb43 Mon Sep 17 00:00:00 2001 From: Juan Villa Date: Mon, 21 Apr 2025 18:52:50 -0400 Subject: [PATCH 1/4] fix(form-core): remove overwritting of falsy values to undefined upon deleting array field This commit fixes #1439 which will preserve existing falsy values within the update function when nameHasChanged is true --- packages/form-core/src/FieldApi.ts | 13 ++- packages/react-form/tests/useForm.test.tsx | 99 ++++++++++++++++++++++ 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/packages/form-core/src/FieldApi.ts b/packages/form-core/src/FieldApi.ts index 18940ac2a..af9526c5e 100644 --- a/packages/form-core/src/FieldApi.ts +++ b/packages/form-core/src/FieldApi.ts @@ -1169,9 +1169,16 @@ export class FieldApi< // The name is dynamic in array fields. It changes when the user performs operations like removing or reordering. // In this case, we don't want to force a default value if the store managed to find an existing value. if (nameHasChanged) { - this.setValue((val) => (val as unknown) || defaultValue, { - dontUpdateMeta: true, - }) + this.setValue( + (val) => { + // Preserve falsy values used for checkboxes or textfields (e.g. false, '') + const newValue = val !== undefined ? val : defaultValue + return newValue + }, + { + dontUpdateMeta: true, + }, + ) } else if (defaultValue !== undefined) { this.setValue(defaultValue as never, { dontUpdateMeta: true, diff --git a/packages/react-form/tests/useForm.test.tsx b/packages/react-form/tests/useForm.test.tsx index ac62ed80b..2cb306468 100644 --- a/packages/react-form/tests/useForm.test.tsx +++ b/packages/react-form/tests/useForm.test.tsx @@ -794,4 +794,103 @@ describe('useForm', () => { expect(fn).toHaveBeenCalledTimes(1) }) + + it('preserves empty string values when removing array elements', async () => { + function Comp() { + const form = useForm({ + defaultValues: { + interests: [{ interestName: '', id: 0 }], + }, + }) + return ( +
+ + {(interestsFieldArray) => ( +
+ + + {interestsFieldArray.state.value.map((row, i) => { + return ( + + {(field) => { + return ( +
+ { + field.handleChange(e.target.value) + }} + /> + +
+ ) + }} +
+ ) + })} +
+

{JSON.stringify(form.getFieldValue('interests'))}

+
+ +
+ )} +
+
+ ) + } + + const { getByTestId } = render() + + // Add 2 interests + await user.click(getByTestId('add-interest')) + await user.click(getByTestId('add-interest')) + + // Remove the first interest + await user.click(getByTestId('remove-interest-0')) + + expect(getByTestId('interests-log').textContent).toBe( + JSON.stringify([ + { id: 1, interestName: '' }, + { id: 2, interestName: '' }, + ]), + ) + }) }) From 3aed5343caed70bbb5d0c8db1724a13ec18cc5da Mon Sep 17 00:00:00 2001 From: Juan Villa Date: Mon, 21 Apr 2025 20:39:04 -0400 Subject: [PATCH 2/4] fix(react-form): Non-index keys in React causing inputs to be uncontrolled when removing array fields #1363 This commit updates React to not render component when in transitory state of stores being out of sync due to array shifting --- packages/react-form/src/useField.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/react-form/src/useField.tsx b/packages/react-form/src/useField.tsx index 38e771a1e..878277083 100644 --- a/packages/react-form/src/useField.tsx +++ b/packages/react-form/src/useField.tsx @@ -478,7 +478,20 @@ export const Field = (< const fieldApi = useField(fieldOptions as any) const jsxToDisplay = useMemo( - () => functionalUpdate(children, fieldApi as any), + () => { + /** + * When field names switch field store and form store are out of sync. + * When in this state, React should not render the component + */ + const isFieldStoreOutofSync = + fieldApi.state.value !== fieldApi.form.getFieldValue(fieldOptions.name) + + if (isFieldStoreOutofSync) { + return null + } + + return functionalUpdate(children, fieldApi as any) + }, /** * The reason this exists is to fix an issue with the React Compiler. * Namely, functionalUpdate is memoized where it checks for `fieldApi`, which is a static type. From 15985e2bf1841bf52b1bab2a5bc96bfde7e1ea75 Mon Sep 17 00:00:00 2001 From: Juan Villa Date: Mon, 21 Apr 2025 21:49:42 -0400 Subject: [PATCH 3/4] fix(form-core): Update validation logic to pull field names from baseStore as the source of logic instead of derived store. --- packages/form-core/src/FormApi.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts index eaa5544fe..1ed9462b0 100644 --- a/packages/form-core/src/FormApi.ts +++ b/packages/form-core/src/FormApi.ts @@ -1292,7 +1292,7 @@ export class FormApi< const errorMapKey = getErrorMapKey(validateObj.cause) for (const field of Object.keys( - this.state.fieldMeta, + this.baseStore.state.fieldMetaBase, // Iterate over the field meta base since it is the source of truth ) as DeepKeys[]) { const fieldMeta = this.getFieldMeta(field) if (!fieldMeta) continue @@ -1326,15 +1326,15 @@ export class FormApi< currentErrorMap?.[errorMapKey] !== newErrorValue ) { this.setFieldMeta(field, (prev) => ({ - ...prev, - errorMap: { - ...prev.errorMap, - [errorMapKey]: newErrorValue, - }, - errorSourceMap: { - ...prev.errorSourceMap, - [errorMapKey]: newSource, - }, + ...prev, + errorMap: { + ...prev.errorMap, + [errorMapKey]: newErrorValue, + }, + errorSourceMap: { + ...prev.errorSourceMap, + [errorMapKey]: newSource, + }, })) } } @@ -1469,7 +1469,7 @@ export class FormApi< const errorMapKey = getErrorMapKey(validateObj.cause) for (const field of Object.keys( - this.state.fieldMeta, + this.baseStore.state.fieldMetaBase, // Iterate over the field meta base since it is the source of truth ) as DeepKeys[]) { const fieldMeta = this.getFieldMeta(field) if (!fieldMeta) continue From d5dc4164ecec3b940bc8a5ddf2862b4be0c72d91 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 22 Apr 2025 01:50:41 +0000 Subject: [PATCH 4/4] ci: apply automated fixes and generate docs --- packages/form-core/src/FormApi.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts index 1ed9462b0..1e4046009 100644 --- a/packages/form-core/src/FormApi.ts +++ b/packages/form-core/src/FormApi.ts @@ -1326,15 +1326,15 @@ export class FormApi< currentErrorMap?.[errorMapKey] !== newErrorValue ) { this.setFieldMeta(field, (prev) => ({ - ...prev, - errorMap: { - ...prev.errorMap, - [errorMapKey]: newErrorValue, - }, - errorSourceMap: { - ...prev.errorSourceMap, - [errorMapKey]: newSource, - }, + ...prev, + errorMap: { + ...prev.errorMap, + [errorMapKey]: newErrorValue, + }, + errorSourceMap: { + ...prev.errorSourceMap, + [errorMapKey]: newSource, + }, })) } }