Files
stack/packages/mosaic/framework/skills/vue-best-practices/reference/render-function-v-for-keys-required.md
T
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

3.9 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Always Include key Props When Rendering Lists in Render Functions HIGH Missing keys cause inefficient re-renders and state bugs in dynamic lists best-practice
vue3
render-function
v-for
performance
key

Always Include key Props When Rendering Lists in Render Functions

Impact: HIGH - Omitting key props when rendering lists with h() or JSX causes Vue to use an inefficient "in-place patch" strategy, leading to performance issues and state bugs when list items have internal state or are reordered.

When rendering lists in render functions using .map(), always include a unique key prop on each item. This is the render function equivalent of using :key with v-for in templates.

Task Checklist

  • Always provide a key prop when mapping over arrays in render functions
  • Use unique, stable identifiers (like id) for keys, not array indices
  • Ensure keys are primitive values (strings or numbers)
  • Never use the same key for different items in the same list

Incorrect:

import { h } from 'vue';

export default {
  setup() {
    const items = ref([
      { id: 1, name: 'Apple' },
      { id: 2, name: 'Banana' },
    ]);

    return () =>
      h(
        'ul',
        // WRONG: No keys - causes inefficient patching
        items.value.map((item) => h('li', item.name)),
      );
  },
};
// WRONG: Using array index as key when list can be reordered
export default {
  setup() {
    const todos = ref([...])

    return () => (
      <ul>
        {todos.value.map((todo, index) => (
          <TodoItem
            key={index}  // Bad: index changes when list reorders
            todo={todo}
          />
        ))}
      </ul>
    )
  }
}

Correct:

import { h } from 'vue';

export default {
  setup() {
    const items = ref([
      { id: 1, name: 'Apple' },
      { id: 2, name: 'Banana' },
    ]);

    return () =>
      h(
        'ul',
        // CORRECT: Unique id as key
        items.value.map((item) => h('li', { key: item.id }, item.name)),
      );
  },
};
// CORRECT: Using stable unique identifier as key
export default {
  setup() {
    const todos = ref([
      { id: 'a1', text: 'Learn Vue' },
      { id: 'b2', text: 'Build app' },
    ]);

    return () => (
      <ul>
        {todos.value.map((todo) => (
          <TodoItem
            key={todo.id} // Good: stable unique identifier
            todo={todo}
          />
        ))}
      </ul>
    );
  },
};
import { h } from 'vue';

export default {
  setup() {
    const users = ref([]);

    return () =>
      h('div', [
        h('h2', 'User List'),
        h(
          'ul',
          users.value.map((user) =>
            h('li', { key: user.email }, [
              // email is unique
              h('span', user.name),
              h('span', ` (${user.email})`),
            ]),
          ),
        ),
      ]);
  },
};

When Index Keys Are Acceptable

Using array indices as keys is acceptable ONLY when:

  1. The list is static and will never be reordered
  2. Items will never be inserted or removed from the middle
  3. Items have no internal component state
// Index is OK here: static list that never changes
const staticLabels = ['Name', 'Email', 'Phone'];

return () =>
  h(
    'tr',
    staticLabels.map((label, index) => h('th', { key: index }, label)),
  );

Why Keys Matter

Without keys, Vue uses an "in-place patch" strategy:

  1. It reuses DOM elements in place
  2. Updates element content to match new data
  3. This breaks when components have internal state or transitions

With proper keys:

  1. Vue tracks each item's identity
  2. Elements are moved, created, or destroyed correctly
  3. Component state is preserved during reorders

Reference