Skip to content

[v2][vue] Field keeps a stale field API after ArrayField removeValue() shifts items, so shifted errors aren't rendered #2391

Description

@awdr74100

Describe the bug

With index keys (as in examples/vue/array), after array.removeValue(0) the form creates a new field API instance for issues[0].title, but the mounted <form.Field :name="issues[0].title"> keeps the old instance. The form meta for issues[0].title contains the error that shifted from the removed row's neighbour, but the slot never renders it.

This happens with markup rendered directly inside the <form.Field> slot. It's unrelated to createFormHook.

Diagnosis (uses the internal _tryGetFieldApi only to compare instances):

Result
Before removal: slot field === form._tryGetFieldApi('issues[0].title') true
After removal: internal instance replaced true
After removal: slot field === internal instance false
After removal: form.getFieldMeta('issues[0].title').errors ["Required"]
After removal: rendered errors none

useField (src/VueForm/useField.lib.ts) only recreates the field API when form, name or resetVersion changes. After removeValue, the name stays the same, so the component isn't moved to the new instance.

Your minimal, reproducible example

<script setup lang="ts">
import { useForm } from '@tanstack/vue-form'

const form = useForm({
  defaultValues: { issues: [{ title: 'A' }, { title: '' }] },
  errorVisibility: () => true,
  validators: [
    {
      triggers: ['change'],
      run: ({ value, createErrorMap }) => {
        const errors = createErrorMap()
        value.issues.forEach((issue, i) => {
          if (!issue.title) (errors.fields as Record<string, string>)[`issues[${i}].title`] = 'Required'
        })
        return errors
      },
    },
  ],
})
</script>

<template>
  <form.ArrayField name="issues" v-slot="{ field: array }">
    <div v-for="(_, i) in array.value" :key="i">
      <form.Field :name="`issues[${i}].title`" v-slot="{ field }">
        <input :value="field.value" @input="field.handleChange(($event.target as HTMLInputElement).value)" />
        <p v-for="error in field.errors" :key="error.message">{{ error.message }}</p>
      </form.Field>
      <button type="button" @click="array.removeValue(i)">Remove</button>
    </div>
  </form.ArrayField>
</template>

Steps to reproduce

  1. Render the component and trigger validation once (for example form.setFieldValue('issues[0].title', 'A')). Row 1 shows "Required".
  2. Click Remove on row 0.
  3. The remaining row is now issues[0] and form.getFieldMeta('issues[0].title').errors is ["Required"], but no error is rendered.

Expected behavior

After removing an item, the mounted Field for each index follows the field API the form now uses for that name, and renders its current value and errors.

How often does this bug happen?

Every time

Platform

  • macOS, Node 24
  • Reproduced in Vitest with happy-dom
  • Vue 3.6.0-rc.8

TanStack Form adapter

vue-form

TanStack Form version

2.0.0-alpha.2, plus the alpha branch at 6e5b809 with the fix from PR #2392 applied. This issue is separate from that PR.

TypeScript version

6.0.3

Additional context

I haven't included a fix, because it touches how useField identifies its field API and I'd rather hear which direction you prefer. Happy to open a PR.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions