diff --git a/.changeset/quiet-slots-stay.md b/.changeset/quiet-slots-stay.md new file mode 100644 index 000000000..5640ec3a8 --- /dev/null +++ b/.changeset/quiet-slots-stay.md @@ -0,0 +1,5 @@ +--- +'@tanstack/form-core': patch +--- + +Fix setting an array value at an index past the end of a non-empty array writing to the wrong index diff --git a/packages/form-core/src/utils.lib.ts b/packages/form-core/src/utils.lib.ts index d1e0005cb..e1325be66 100644 --- a/packages/form-core/src/utils.lib.ts +++ b/packages/form-core/src/utils.lib.ts @@ -181,7 +181,8 @@ export function setBy(obj: any, _path: any, updater: Updater) { if (Array.isArray(parent) && typeof key === 'number') { const prefix = parent.slice(0, key) return [ - ...(prefix.length ? prefix : new Array(key)), + ...prefix, + ...new Array(key - prefix.length), doSet(parent[key]), ...parent.slice(key + 1), ] diff --git a/packages/form-core/tests/FormApi/FormApi.lib.spec.ts b/packages/form-core/tests/FormApi/FormApi.lib.spec.ts index 3dc9393ff..2aa39932f 100644 --- a/packages/form-core/tests/FormApi/FormApi.lib.spec.ts +++ b/packages/form-core/tests/FormApi/FormApi.lib.spec.ts @@ -742,6 +742,33 @@ describe('InternalFormApi - setFieldValue', () => { expect(field.value).toEqual(['B']) expect(form.defaultValues).toEqual({ items: ['a'] }) }) + it('sets a value at an index past the end of a non-empty array', () => { + const form = new InternalFormApi({ + defaultValues: { phones: ['555-0000'] }, + }) + const fields = [0, 1, 2, 3].map((i) => + form._getOrCreateFieldApi({ name: `phones[${i}]` as const }), + ) + + fields[3]!.handleChange('555-0003') + + expect(form.getFieldValue('phones[3]')).toBe('555-0003') + expect(form.state.values.phones).toStrictEqual([ + '555-0000', + undefined, + undefined, + '555-0003', + ]) + + fields[1]!.handleChange('555-0001') + + expect(fields.map((field) => field.value)).toStrictEqual([ + '555-0000', + '555-0001', + undefined, + '555-0003', + ]) + }) }) describe('InternalFormApi - reset', () => { @@ -1053,6 +1080,21 @@ describe('InternalFormApi - resetField', () => { expect(form.state.values).toEqual({ person: { name: 'Carol' } }) expect(form.state.isTouched).toBe(true) }) + it('restores the default at its own index when the array is shorter than the defaults', () => { + const form = new InternalFormApi({ + defaultValues: { phones: ['a', 'b', 'c', 'd'] }, + }) + + form.setFieldValue('phones', ['a']) + form.resetField('phones[3]') + + expect(form.state.values.phones).toStrictEqual([ + 'a', + undefined, + undefined, + 'd', + ]) + }) }) describe('InternalFormApi - deleteField', () => { diff --git a/packages/form-core/tests/utils.test.ts b/packages/form-core/tests/utils.test.ts index be555831b..3451d66e1 100644 --- a/packages/form-core/tests/utils.test.ts +++ b/packages/form-core/tests/utils.test.ts @@ -99,7 +99,11 @@ describe('utils', () => { items: [undefined, undefined, { name: 'Ada' }], }) - expect(setBy(['first'], '[2]', 'third')).toEqual(['first', 'third']) + expect(setBy(['first'], '[2]', 'third')).toStrictEqual([ + 'first', + undefined, + 'third', + ]) expect(setBy([], '[2]', 'third')).toEqual([undefined, undefined, 'third']) expect(setBy(undefined, [undefined], 'third')).toEqual([undefined, 'third']) })