Vue 3's v-for: Common Patterns, Eight Traps, and the Key Mistakes That Break Lists
A list that renders the wrong row or an input that carries another row's value is usually a v-for misuse, not a Vue bug. The concrete consequence is expensive rework in dynamic forms, draggable lists, and async data screens, where these eight patterns prevent the worst of it.
When Vue 3 developers render lists, v-for is the workhorse: arrays with (item, index), objects with (value, key, index), plain numbers for fixed counts, template wrappers that add no extra DOM, and custom components that still require a key. Filtering belongs in computed properties, not inside the template loop, so the render path stays cheap and the template stays readable.
The main value of this collection is its catalog of eight failure modes. v-for outranks v-if on the same element, so the official advice is to keep them apart, usually by moving v-if to an inner template or pre-filtering with computed. Binding key to index breaks once items are inserted, deleted, or dragged, because Vue's diff reuses the wrong DOM nodes and form inputs end up holding values from the wrong rows. Direct index assignment on a reactive array may not trigger an update; splice or a fresh array reference works. Duplicate keys produce warnings and random corruption.
Remaining traps live in interactive and async flows. A string ref on a looped component captures only the last instance; function refs collect them all. Sharing one v-model variable across looped inputs makes every row type in sync, so each input should bind to its own item's property. Async lists initialized to null throw a Symbol.iterator runtime error, and the fix is an empty array default plus an empty-state branch. Adding a brand-new property to a reactive object can silently fail to update the view; replacing the object reference restores the update.
Proxy-based reactivity is better than Vue 2's, but it has not removed the need for discipline: direct index writes to an array can still miss updates, so teams have to standardize on splice or reference replacement.
The input cross-fill bug from index keys is not a cosmetic rendering issue; editable lists and dynamic forms silently attach UI state to the wrong data, which is far harder to catch than a crashed render.
Vue's official warning against v-for with v-if is clear, but it remains the number one pitfall in practice, so linter rules or code review would do more than another doc paragraph.
The null-iterator crash from async data is trivial to avoid with an empty array default, yet it keeps happening because developers mirror backend nulls instead of modeling pending state as empty.