UUID as React and Vue List Keys: When and How
- React and Vue use the key prop to track which items have changed, been added, or removed.
- UUIDs make excellent stable keys when items have server-assigned IDs — use those directly.
- Never generate a new UUID inside render() or template — it defeats the purpose of key.
- For client-side-only ephemeral lists (no server ID), assign a UUID once when creating the item.
Table of Contents
React's key prop and Vue's :key binding exist for one reason: to tell the framework which DOM node corresponds to which data item across renders. Use a stable unique ID and the framework reuses the right DOM node. Use an unstable value (like array index or a freshly generated UUID) and you get unnecessary re-mounts, lost input state, and broken animations. UUIDs are excellent keys — but only when assigned at the right time.
Why key Matters: How React and Vue Track List Items
When React (or Vue) renders a list and the list changes, it needs to figure out what changed. The key prop is the identifier it uses:
- Same key, same position → update the existing DOM node in place.
- New key → create a new DOM node (mount).
- Missing key → remove the DOM node (unmount).
If every render generates a new UUID for the same item, React sees a new key every time — it unmounts and remounts the component instead of updating it. This loses input state, triggers mount animations again, and is slower than a simple update.
// ❌ WRONG — generates new UUID every render
{items.map(item => (
<Item key={crypto.randomUUID()} data={item} />
))}
// ✓ CORRECT — use the stable ID from your data
{items.map(item => (
<Item key={item.id} data={item} />
))}
Best Case: Use the UUID from Your Database
If your items come from a database with UUID primary keys, the answer is simple — use the existing ID:
// React — fetched orders from API, each has a UUID id
function OrderList({ orders }) {
return (
<ul>
{orders.map(order => (
<li key={order.id}>
{order.customerEmail} — ${order.total}
</li>
))}
</ul>
);
}
<!-- Vue 3 -->
<ul>
<li v-for="order in orders" :key="order.id">
{{ order.customerEmail }} — {{ order.total }}
</li>
</ul>
The UUID from your API response is already globally unique and stable across all renders. Nothing else needed.
Sell Custom Apparel — We Handle Printing & Free ShippingClient-Only Lists: Assign UUID Once at Creation Time
For local state that never hits a server — like a dynamic form where users add rows — assign the UUID when the item is created, not during render:
import { useState } from 'react';
function DynamicForm() {
const [rows, setRows] = useState([
{ id: crypto.randomUUID(), label: '', value: '' }
]);
const addRow = () => {
setRows(prev => [
...prev,
{ id: crypto.randomUUID(), label: '', value: '' } // UUID assigned HERE
]);
};
const removeRow = (id) => {
setRows(prev => prev.filter(row => row.id !== id));
};
return (
<div>
{rows.map(row => (
<FormRow key={row.id} row={row} onRemove={removeRow} />
// key={row.id} is stable — same UUID across all re-renders
))}
<button onClick={addRow}>Add Row</button>
</div>
);
}
The UUID is generated once in addRow() and stored in state. Every time the component re-renders, row.id is the same value — so React reuses the DOM node instead of remounting it.
Array Index vs UUID: Which Is Worse?
Many tutorials say "use array index as key if you don't have an ID." This is worse than UUID in reordering scenarios:
- Reorder items: index keys stay 0, 1, 2 — React thinks nothing moved. Input state in position 0 stays with position 0, not with the item that was there.
- Insert at beginning: every item gets a new index. React remounts every item.
UUID keys are strictly better than index keys whenever items can be reordered, filtered, or deleted. Array index is only safe for static lists that never change order.
// ❌ Index key — breaks on reorder
{items.map((item, index) => <Item key={index} {...item} />)}
// ✓ UUID key — stable on reorder
{items.map(item => <Item key={item.id} {...item} />)}
React 18+: useId Hook for Component-Scoped IDs
React 18 introduced useId() — a hook that generates a stable, unique ID scoped to a component instance. It's designed for accessibility attributes (htmlFor, aria-labelledby), not list keys:
import { useId } from 'react';
function FormField({ label }) {
const id = useId(); // stable across renders, unique per instance
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} type="text" />
</div>
);
}
useId() is not for list keys — it generates IDs based on component tree position, which changes when items are reordered. For list keys, stick with UUID from your data or from state.
Use the free UUID generator above to create test IDs for React and Vue unit tests, Storybook stories, or mock API responses.
Generate UUID v4 Values for React and Vue Tests
The Cheetah UUID Generator produces RFC-compliant UUID v4 strings — paste them as mock IDs in Storybook stories, Jest test fixtures, or Vitest mocks.
Open Free UUID GeneratorFrequently Asked Questions
Can I use crypto.randomUUID() directly as a key in JSX?
Only if you call it outside render — stored in state or computed from stable data. Calling crypto.randomUUID() directly inside map() generates a new UUID on every render, causing constant remounts.
Does using UUID keys improve React performance?
Stable UUID keys improve performance compared to index keys for reordering scenarios, because React correctly identifies unchanged items and skips their updates. Random UUID keys (generated in render) destroy performance.
What's the React useId hook for if not for list keys?
useId() is for accessibility — linking labels to inputs via htmlFor/id, aria-labelledby, and similar attribute pairs. It generates a component-tree-stable ID that works in server-side rendering. It's explicitly not designed for list keys.
Should Vue :key and React key be the same field?
They serve the same purpose and the same rules apply — use a stable, unique identifier from your data. The implementation is different (Vue uses :key directive, React uses key prop) but the best practices are identical.

