You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(form-core): [v2] set array values at the given index past the end - #2479
Writing past the end of a non-empty array puts the value at length instead of at the index. With defaultValues: { phones: ['555-0000'] }, typing into phones[3] stores the value at phones[1], where a later phones[1] input overwrites it. resetField('phones[3]') on a shortened array has the same problem.
setBy only pads the gap when the slice before the index is empty (utils.lib.ts#L184), so setBy([], '[2]', 'third') already gives [undefined, undefined, 'third']. This pads it in both cases:
One existing assertion changes.sets array values and preserves sparse indexes (added in 7de9faa) expects setBy(['first'], '[2]', 'third') to equal ['first', 'third']. It now expects ['first', undefined, 'third'], matching the test name and the empty-array assertion below it. If the old result is intended, could you point me to the case it covers? If you'd rather reject past-end writes like the array methods do (warn and no-op), I can switch to that.
Tests: typing into phones[3] then phones[1] keeps both values; resetField('phones[3]') after shortening restores the default at index 3. Both, and the changed assertion, fail on alpha and pass with the fix.
Behavior change: the array grows to index + 1 with undefined in the gap, so field.value.map(...) and schemas like z.array(z.string()) will see those slots. A numeric key on a missing parent still creates an object (#993).
The same code is on main (v1); happy to open a backport.
I have tested code changes locally with pnpm test:pr, or these tests do not apply to this pull request.
I fully understand the code in this pull request, including any code generated with AI assistance.
🚀 Release Impact
This change affects published code, and I have generated a changeset.
This change is docs/CI/dev-only (no release).
Summary by CodeRabbit
Bug Fixes
Fixed setting form values at indexes beyond an array’s end: existing values are preserved, intermediate indexes remain empty, and those fields can be updated or reset independently.
setBy only padded the array when the slice before the index was empty,
so writing past the end of a non-empty array placed the value at
`length` instead of at the index. Pad the gap with undefined, which is
what already happens for empty arrays.
setBy now fills array gaps based on the existing prefix length when setting an indexed value. Tests cover updates and resets beyond the end of an array, and a Changeset records a patch release for @tanstack/form-core.
Changes: Array index updates
Layer / File(s)
Summary
Array gap filling and field behavior packages/form-core/src/utils.lib.ts, packages/form-core/tests/FormApi/FormApi.lib.spec.ts, packages/form-core/tests/utils.test.ts, .changeset/quiet-slots-stay.md
setBy preserves the existing prefix and fills intermediate indexes with undefined. Tests cover setting and resetting indexes beyond the array’s end. A Changeset records the patch release.
Writing to an array index past the end now fills the gap with undefined and puts the value at the requested index. This fixes the previous misplacement. Code that assumes every element of a string[] field is defined, such as value.map(p => p.trim()), can now hit undefined entries. The PR states this behavior is intended, so owners should be aware of it, and it is not clearly a blocker.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title names the main change: array writes past the end of a non-empty array now pad the gap so the value lands at the requested index. It also names the affected package (form-core) and the fix ty…
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Description check
The description follows the repository template. It explains the bug: writing past the end of a non-empty array stores the value at length instead of the requested index. It names the fix in setBy…
Full details: Title check
Explanation
The title names the main change: array writes past the end of a non-empty array now pad the gap so the value lands at the requested index. It also names the affected package (form-core) and the fix type. It matches the changes in utils.lib.ts, the tests, and the changeset.
Full details: Description check
Explanation
The description follows the repository template. It explains the bug: writing past the end of a non-empty array stores the value at length instead of the requested index. It names the fix in setBy in utils.lib.ts, and it lists the test coverage. It also flags the one changed existing assertion and the behavior change for consumers such as field.value.map(...) and z.array(z.string()). The Changes section is detailed. The checklist is filled in, and the release impact box is ticked with a changeset generated, which matches the raw summary's changeset file. The description does not explicitly name the test command result beyond the checkbox, but the checklist covers it.
Finishing Touches
Generate unit tests (beta)
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
The reason will be displayed to describe this comment to others. Learn more.
Additional comments: 1
packages/form-core/src/utils.lib.ts-185-185: 1
185-185: Major · Data Integrity & Integration · Heavy lift
Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.
Preserve the form value's element-type contract.
For the new phones: string[] test, writing phones[3] puts undefined in form.state.values.phones, which remains typed as string[]. A consumer such as phones.map(phone => phone.trim()) then throws. Prevent the invalid values or expose them through the form's type and API contract.
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🎯 Changes
Writing past the end of a non-empty array puts the value at
lengthinstead of at the index. WithdefaultValues: { phones: ['555-0000'] }, typing intophones[3]stores the value atphones[1], where a laterphones[1]input overwrites it.resetField('phones[3]')on a shortened array has the same problem.setByonly pads the gap when the slice before the index is empty (utils.lib.ts#L184), sosetBy([], '[2]', 'third')already gives[undefined, undefined, 'third']. This pads it in both cases:One existing assertion changes.
sets array values and preserves sparse indexes(added in 7de9faa) expectssetBy(['first'], '[2]', 'third')to equal['first', 'third']. It now expects['first', undefined, 'third'], matching the test name and the empty-array assertion below it. If the old result is intended, could you point me to the case it covers? If you'd rather reject past-end writes like the array methods do (warn and no-op), I can switch to that.Tests: typing into
phones[3]thenphones[1]keeps both values;resetField('phones[3]')after shortening restores the default at index 3. Both, and the changed assertion, fail onalphaand pass with the fix.Behavior change: the array grows to
index + 1withundefinedin the gap, sofield.value.map(...)and schemas likez.array(z.string())will see those slots. A numeric key on a missing parent still creates an object (#993).The same code is on
main(v1); happy to open a backport.✅ Checklist
pnpm test:pr, or these tests do not apply to this pull request.🚀 Release Impact
Summary by CodeRabbit