Skip to content

Fields in uuid-keyed array items transiently go undefined on removal again: #1729 fixed it in 1.26.0, #1893 regressed it in 1.27.0 #2238

Description

@hendrix-io

Describe the bug

Hey! This is the same symptom as #1518 / #1363 — array items keyed by uuid instead of index, remove one that isn't the last, and fields in the surviving items render once with state.value === undefined before snapping back. If the render prop does anything strict with the value (an invariant, Object.entries(value), an in check, a controlled input), that one render pass blows up — even though form state itself is fine the whole time.

Here's the fun part though: this was actually fixed by #1729 in 1.26.0, and then regressed a week later. I bisected releases with the same repro:

react-form version result
≤ 1.25.x broken (original #1518 bug)
1.26.0 fixed
1.27.0 broken again
1.33.1 (latest I tested) still broken

The regressing commit is 8afbfc39 (#1893, "fix: react compiler should now work in all edgecases", shipped in 1.27.0).

What I think is happening

#1729 fixed this in useField by recreating the FieldApi synchronously when the name changes:

const fieldApi = useMemo(() => new FieldApi({ ... }), [opts.form, opts.name])

#1893 swapped that for a setState-during-render pattern:

if (prevOptions.form !== opts.form || prevOptions.name !== opts.name) {
  setFieldApi(new FieldApi({ ...opts }))
  setPrevOptions({ form: opts.form, name: opts.name })
}

My hunche: when you setState during render, React discards the pass and re-renders. Right?? lol. But the current invocation still runs to completion with the old state. So the render prop executes exactly once with the new name prop and the old FieldApi, whose store now derives undefined for the path that no longer exists.

I tested this on 1.33.1 after removing item 0 of 2:

name prop:                        sections[0].sectionTitle   ← new, correct
field.name (FieldApi instance):   sections[1].sectionTitle   ← stale
field.state.value:                undefined
form.getFieldValue(nameProp):     "Section Content 2"        ← form state is fine!

So the form knows the value (getFieldValue), but the field is looking it up under stale state and finds nothing. People on #1518 spotted this exact mismatch.

Your minimal, reproducible example

https://stackblitz.com/edit/tanstack-form-y5yg6fxf?file=src%2Fapp.tsx

Steps to reproduce

Two forks of the same StackBlitz, only the pinned version differs (both log the installed version to the console on load):

Failing on 1.33.1: StackBlitz w/1.33.1
Passing on 1.26.0: StackBlitz w/1.26.0

Steps: click Add Section twice, open the console, click the first Remove Section. On 1.33.1 you get a transient undefined! console.error with the fingerprint above; on 1.26.0, its all guud.

The repro's render code is deliberately tolerant (value ?? '') so the console log is the evidence but any strict render code turns this into a hard crash. Remove the ?? '' and you get React's controlled→uncontrolled warning (#1363's symptom). In my actual app (config-driven dynamic fields doing fieldName in field.state.value), this surfaces as an unhandled render error: Cannot use 'in' operator to search for '...' in undefined. The original #1518 repro with invariant shows the crashing form too.

Expected behavior

As a user, I expected removing an item from a uuid-keyed array field to remove just that item, with the remaining fields continuing to show their values. Instead, fields in the items after the removed one briefly render with state.value === undefined even though the value is still in form state.

A field should report the value at the path it was asked to render. In the bad pass, the path has a value (getFieldValue returns it) but the field reads undefined. I believe this is because for that one render, the name prop and field.name point at two different array indexes. On 1.26.0 this never happens. Happy to re-run my repro against any candidate fix.

Bonus footgun I hit while looking for workarounds

Adding a defaultValue prop to the affected field looks like a fix (no more undefined) but actually loses data on 1.27+: FieldApi.mount() overwrites an untouched field's existing form value with defaultValue, so any keyed remount clobbers values that were already pushed/loaded into form state. Flagging it so nobody grabs that as the workaround. (The workaround that does hold up: keying the inner form.Field by its full name path so it remounts with the correct name.)

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

  • OS: any (repro'd on Linux + jsdom and in-browser)
  • React: 19.x
  • @tanstack/react-form: fails 1.27.0–1.33.1, passes on 1.26.0

TanStack Form adapter

@tanstack/react-form

TanStack Form version

1.33.1

TypeScript version

No response

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions