useFieldArray
A list of fields: order items, phones, participants. Gives stable keys for v-for and row operations. When rows are removed or reordered, touched marks move with them — an error never jumps to a neighbouring row.
Signature
ts
function useFieldArray<T>(path: string): FieldArray<T>
function useFieldArray<T>(form: Form, path: string): FieldArray<T>| Returns | Description |
|---|---|
fields | { key, index, path, value }[] — key for v-for, path for errors and touch |
append(value), prepend(value) | Add a row to the end / start |
insert(index, value) | Insert a row |
remove(index) | Remove a row |
move(from, to), swap(a, b) | Reorder rows |
replace(values) | Replace the whole list |
Without form the form comes from the context. If there is no array in values, it is created. Direct changes (form.values.items.push(...)) work too — new rows get keys.
Example
vue
<script setup lang="ts">
const Order = object({
items: pipe(array(object({ title: pipe(string(), minLength(2)), qty: pipe(coerce.number(), min(1)) })), minLength(1)),
});
const form = useForm(Order, { initial: { items: [{ title: '', qty: '1' }] } });
const items = useFieldArray<{ title: string; qty: string }>(form, 'items');
</script>
<template>
<div v-for="field in items.fields.value" :key="field.key">
<input v-model="field.value.title" :name="`${field.path}.title`" @blur="form.touch(`${field.path}.title`)" />
<span>{{ form.errors[`${field.path}.title`] }}</span>
<input v-model="field.value.qty" :name="`${field.path}.qty`" @blur="form.touch(`${field.path}.qty`)" />
<button type="button" :disabled="field.index === 0" @click="items.move(field.index, field.index - 1)">↑</button>
<button type="button" @click="items.remove(field.index)">×</button>
</div>
<button type="button" @click="items.append({ title: '', qty: '1' })">+</button>
</template>Why not the index as :key: removing the first row shifts all indexes, and Vue reuses the wrong rows' inputs — focus and typed text "jump".