skills/react-key-prop/SKILL.md
Guidance on React key prop best practices for stable keys and reconciliation. Use when reviewing React components, rendering lists, or troubleshooting key-related rendering bugs.
npx skillsauth add Thomashighbaugh/opencode react-key-propInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
key Prop Best PracticesQuick-reference guide for using the key prop correctly in React.
.map()Keys must be stable, unique, and predictable. They tell React how to match elements between renders during reconciliation.
// ✅ Good: stable, unique, predictable
items.map((item) => <ListItem key={item.id} item={item} />);
// ❌ Bad: unstable, changes every render
items.map((item, index) => <ListItem key={index} item={item} />);
// ❌ Bad: non-unique
items.map((item) => <ListItem key={item.type} item={item} />);
// ❌ Bad: random — causes full remount every render
items.map((item) => <ListItem key={Math.random()} item={item} />);
Using array index as key is the most common React key bug.
| Scenario | Effect | |----------|--------| | Items reordered | React reuses wrong component instances — state, refs, and DOM attached to wrong items | | Items inserted/deleted at start | All subsequent indices shift — every component remounts or gets wrong state | | List is filtered | Same index shift problem | | Items have local state (inputs, checkboxes) | State persists on wrong elements — user sees stale/incorrect data |
// ✅ Acceptable: static, read-only, never changes
const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
days.map((day, i) => <span key={i}>{day}</span>);
| Source | Stable? | Notes |
|--------|---------|-------|
| Database ID (item.id) | ✅ Yes | Best choice — unique, stable, persistent |
| UUID generated at creation | ✅ Yes | Good for optimistic UI before server response |
| Content hash | ✅ Yes | Works when no ID exists — hash of item content |
| Array index | ❌ No | Breaks on reorder/insert/delete |
| Math.random() | ❌ No | Changes every render — full remount |
| Date.now() | ❌ No | Changes every render — full remount |
| crypto.randomUUID() | ❌ No | Changes every render — full remount |
Keys belong on the element returned from .map(), not on nested children:
// ✅ Correct: key on the outermost returned element
items.map((item) => (
<div key={item.id}>
<ListItem item={item} />
</div>
));
// ❌ Wrong: key on a child, not the map return
items.map((item) => (
<div>
<ListItem key={item.id} item={item} />
</div>
));
// ✅ Correct: key on Fragment shorthand (<> doesn't accept key)
items.map((item) => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.def}</dd>
</React.Fragment>
));
Keys are not passed as props — they're internal to React's reconciliation:
function ListItem({ item }: { item: Item }) {
// ❌ Can't access props.key — it's reserved
return <div>{item.name}</div>;
}
// ✅ Key is set on the JSX element, not inside the component
items.map((item) => <ListItem key={item.id} item={item} />);
| Key change | React behavior | |------------|----------------| | Same key, same position | Re-render (update existing instance) | | Same key, different position | Move (reorder DOM nodes, preserve state) | | Different key | Unmount old, mount new (reset all state) | | Key removed | Unmount (destroy state, refs, effects) |
key to force remountSometimes you want a fresh component instance (e.g., resetting a form):
// Remounts UserForm when userId changes — resets all internal state
<UserForm key={userId} userId={userId} />
// ❌ Bad: type is not unique across items
items.map((item) => <Item key={item.type} />);
// ✅ Good: combine with another field
items.map((item) => <Item key={`${item.type}-${item.id}`} />);
// ❌ Syntax error: <> doesn't accept key
items.map((item) => (
< key={item.id}> {/* ❌ */}
<dt>{item.term}</dt>
<dd>{item.def}</dd>
</>
));
// ✅ Use React.Fragment
items.map((item) => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.def}</dd>
</React.Fragment>
));
// ✅ React uses the value itself as key for primitives
const numbers = [1, 2, 3];
numbers.map((n) => <li>{n}</li>);
// React internally uses the value as key when none is provided
// But explicit key is still better for clarity
// ✅ DO: stable unique ID
items.map((item) => <Item key={item.id} item={item} />);
// ✅ DO: content hash when no ID
items.map((item) => <Item key={hashContent(item)} item={item} />);
// ✅ DO: composite key when needed
items.map((item) => <Item key={`${item.type}-${item.seq}`} item={item} />);
// ✅ DO: key on Fragment for multiple elements
items.map((item) => (
<React.Fragment key={item.id}>
<ChildA item={item} />
<ChildB item={item} />
</React.Fragment>
));
// ❌ DON'T: array index (unless static read-only list)
items.map((item, i) => <Item key={i} item={item} />);
// ❌ DON'T: random/date keys
items.map((item) => <Item key={Math.random()} item={item} />);
// ❌ DON'T: key on child instead of map return
items.map((item) => (
<div>
<Item key={item.id} item={item} />
</div>
));
tools
Create valid, type-safe TypeScript tools for OpenCode — generates correct boilerplate, typed interfaces, and handler stubs. Use when building new tools for global config or per-project .opencode/tools/.
testing
Explore multiple solution branches in parallel, evaluate each, and recommend the best path. Use when the user explicitly invokes /ideation tree-of-thoughts or asks for "tree of thought" / "branching exploration".
testing
Run multiple independent reasoning passes and find consensus. Use when the decision is critically important, the stakes are high, or the user explicitly requests "run it multiple times" / "check consistency" / "self-consistency".
testing
Optimization by PROmpting — generate candidate prompt variations, test each against a benchmark, and report the best performer. Use when the user invokes /ideation opro or asks for "prompt optimization" / "OPRO".