Skip to content

Commit 3538898

Browse files
committed
feat(core): add skills for react key rendering pattern
1 parent da1f51c commit 3538898

7 files changed

Lines changed: 168 additions & 31 deletions

File tree

docs/hook-design-principles.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -267,14 +267,15 @@ function useFetch<T>(url: string) { const res = await axios.get(url); ... }
267267

268268
> Separate document: [react-hook-usage-patterns.md](./react-hook-usage-patterns.md)
269269
270-
16 patterns based on React official docs (react.dev), with source URLs and quotes (U1-U17, U4 removed):
270+
17 patterns based on React official docs (react.dev), with source URLs and quotes (U1-U18, U4 removed):
271271

272-
| Category | Count | Key Patterns |
273-
| ------------ | ---------------- | -------------------------------------------------------------------------------- |
274-
| State Design | U1-U3, U5-U7 (6) | Derive don't sync, don't mirror props, useRef, discriminated unions, group state |
275-
| Effect Usage | U8-U14 | Effects for sync only, no chains, key reset, async cleanup |
276-
| Memoization | U15-U16 | useMemo >= 1ms, useCallback + memo() only |
277-
| Hook Design | U17 | No lifecycle wrappers, extract reusable stateful logic only |
272+
| Category | Count | Key Patterns |
273+
| ---------------------- | ---------------- | -------------------------------------------------------------------------------- |
274+
| State Design | U1-U3, U5-U7 (6) | Derive don't sync, don't mirror props, useRef, discriminated unions, group state |
275+
| Effect Usage | U8-U14 | Effects for sync only, no chains, key reset, async cleanup |
276+
| Memoization | U15-U16 | useMemo >= 1ms, useCallback + memo() only |
277+
| Hook Design | U17 | No lifecycle wrappers, extract reusable stateful logic only |
278+
| Identity and Rendering | U18 | Stable keys, intentional remounts, rendering efficiency |
278279

279280
---
280281

@@ -301,7 +302,8 @@ packages/plugin/ (planned)
301302
├── .codex-plugin/plugin.json
302303
├── principles/ ← Shared principles single source
303304
├── skills/
304-
│ ├── react-hook-review/SKILL.md ← C1-C14 + U1-U17 checklist
305+
│ ├── react-hook-review/SKILL.md ← C1-C14 + U1-U18 checklist
306+
│ ├── react-hook-review/references/key-rendering.md
305307
│ └── react-hook-writing/
306308
│ ├── SKILL.md ← Themed guide
307309
│ └── references/patterns.md ← 3 hook implementations
@@ -342,7 +344,7 @@ packages/plugin/ (planned)
342344
| Phase | Content | Output |
343345
| ----- | ----------------------------------------- | ------------------------------ |
344346
| 1 | Directory + plugin.json + README | `packages/plugin/` structure |
345-
| 2 | react-hook-review SKILL.md | C1-C14 + U1-U17 checklist |
347+
| 2 | react-hook-review SKILL.md | C1-C14 + U1-U18 checklist |
346348
| 3 | react-hook-writing SKILL.md + patterns.md | Themed guide + 3 hook examples |
347349
| 4 | Generalization validation (grep) | 0 project references |
348350
| 5 | Plugin validate + local test | Working confirmation |

docs/ko/hook-design-principles.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -257,14 +257,15 @@ function useFetch<T>(url: string) { const res = await axios.get(url); ... }
257257

258258
> 별도 문서: [react-hook-usage-patterns.md](./react-hook-usage-patterns.md)
259259
260-
React 공식 문서(react.dev) 기반 16개 패턴 (U1-U17, U4 제거):
260+
React 공식 문서(react.dev) 기반 17개 패턴 (U1-U18, U4 제거):
261261

262-
| 카테고리 | 개수 | 핵심 |
263-
| ------------ | ------------------ | -------------------------------------------------------------- |
264-
| State Design | U1-U3, U5-U7 (6개) | 파생값 계산, props 복사 금지, useRef, union type, state 그룹화 |
265-
| Effect Usage | U8-U14 | effect는 외부 동기화 전용, 체인 금지, key 리셋, 비동기 cleanup |
266-
| Memoization | U15-U16 | useMemo 1ms+, useCallback + memo() 조합만 |
267-
| Hook Design | U17 | lifecycle wrapper 금지, 구체적 목적 훅만 |
262+
| 카테고리 | 개수 | 핵심 |
263+
| ---------------------- | ------------------ | -------------------------------------------------------------- |
264+
| State Design | U1-U3, U5-U7 (6개) | 파생값 계산, props 복사 금지, useRef, union type, state 그룹화 |
265+
| Effect Usage | U8-U14 | effect는 외부 동기화 전용, 체인 금지, key 리셋, 비동기 cleanup |
266+
| Memoization | U15-U16 | useMemo 1ms+, useCallback + memo() 조합만 |
267+
| Hook Design | U17 | lifecycle wrapper 금지, 구체적 목적 훅만 |
268+
| Identity and Rendering | U18 | 안정적인 key, 의도적 remount, 렌더링 효율 |
268269

269270
---
270271

@@ -291,7 +292,8 @@ packages/plugin/ (planned)
291292
├── .codex-plugin/plugin.json
292293
├── principles/ ← 공통 원칙 Single Source
293294
├── skills/
294-
│ ├── react-hook-review/SKILL.md ← C1-C14 + U1-U17 체크리스트
295+
│ ├── react-hook-review/SKILL.md ← C1-C14 + U1-U18 체크리스트
296+
│ ├── react-hook-review/references/key-rendering.md
295297
│ └── react-hook-writing/
296298
│ ├── SKILL.md ← 테마별 가이드
297299
│ └── references/patterns.md ← 구현 예시 3개
@@ -332,7 +334,7 @@ packages/plugin/ (planned)
332334
| Phase | 내용 | 산출물 |
333335
| ----- | ----------------------------------------- | --------------------------- |
334336
| 1 | 디렉토리 + plugin.json + README | `packages/plugin/` 구조 |
335-
| 2 | react-hook-review SKILL.md | C1-C14 + U1-U17 체크리스트 |
337+
| 2 | react-hook-review SKILL.md | C1-C14 + U1-U18 체크리스트 |
336338
| 3 | react-hook-writing SKILL.md + patterns.md | 테마별 가이드 + 3개 훅 예시 |
337339
| 4 | 일반화 검증 (grep) | 프로젝트 참조 0건 |
338340
| 5 | 플러그인 validate + 로컬 테스트 | 동작 확인 |

docs/ko/react-hook-usage-patterns.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
> 출처: React 공식 문서 (react.dev)
44
> 관련: [Hook Design Principles](./hook-design-principles.md)
55
6-
코딩 스타일이 아닌 **hooks를 올바르게 사용하는 패턴**. 16개 원칙 (U1-U17, U4 제거).
6+
코딩 스타일이 아닌 **hooks를 올바르게 사용하는 패턴**. 17개 원칙 (U1-U18, U4 제거).
77

88
---
99

@@ -159,3 +159,25 @@ memo() 없는 자식에 stable reference → 리렌더 방지 효과 없음.
159159

160160
lifecycle wrapper(`useMount`, `useEffectOnce`) 금지. 구체적 동기화 목적 훅(`useWindowSize`, `useOnlineStatus`)만.
161161
추출 기준: 동일 state+effect 패턴이 2개+ 컴포넌트에서 반복되는지?
162+
163+
---
164+
165+
## Identity and Rendering (1개)
166+
167+
### U18. 리스트와 서브트리 identity에는 안정적인 key 사용
168+
169+
동적인 리스트에는 데이터에서 온 안정적이고 sibling 사이에서 유일한 key를 사용한다. key는 insert/delete/reorder 업데이트에서 같은 아이템을 다시 매칭해 state를 보존하고 불필요한 DOM/component 재생성을 줄인다. 반대로 서브트리를 리셋해야 할 때만 의도적으로 key를 바꾼다.
170+
171+
> 📖 [Rendering Lists — Keeping list items in order with key](https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key)
172+
> 📖 [Preserving and Resetting State — Resetting state with a key](https://react.dev/learn/preserving-and-resetting-state#option-2-resetting-state-with-a-key)
173+
174+
```tsx
175+
// ❌ 중복되거나 불안정한 identity
176+
items.map(item => <Row key={item.label} item={item} />);
177+
178+
// ✅ 데이터 기반의 안정적인 identity
179+
items.map(item => <Row key={item.id} item={item} />);
180+
181+
// ✅ 의도적인 서브트리 리셋
182+
<Chat key={recipient.id} recipient={recipient} />;
183+
```

docs/react-hook-usage-patterns.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
> Related: [Hook Design Principles](./hook-design-principles.md)
55
> Korean version: [ko/react-hook-usage-patterns.md](./ko/react-hook-usage-patterns.md)
66
7-
Patterns for **correctly using hooks** — not coding style, but React-specific best practices. 16 principles (U1-U17, U4 removed).
7+
Patterns for **correctly using hooks** — not coding style, but React-specific best practices. 17 principles (U1-U18, U4 removed).
88

99
---
1010

@@ -192,3 +192,25 @@ No lifecycle wrappers (`useMount`, `useEffectOnce`). Only purpose-specific hooks
192192
Extraction criterion: Does the same state+effect pattern repeat in 2+ components?
193193

194194
> 📖 [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) > _"Custom Hooks let you share stateful logic, not state itself."_
195+
196+
---
197+
198+
## Identity and Rendering (1)
199+
200+
### U18. Use Stable Keys for List and Subtree Identity
201+
202+
Use stable, unique sibling keys from data for dynamic lists. Keys let React match the same item across insert/delete/reorder updates, preserving state and avoiding unnecessary DOM/component recreation. Change a key intentionally when a subtree should remount and reset.
203+
204+
> 📖 [Rendering Lists — Keeping list items in order with key](https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key) > _"Keys tell React which array item each component corresponds to"_
205+
> 📖 [Preserving and Resetting State — Resetting state with a key](https://react.dev/learn/preserving-and-resetting-state#option-2-resetting-state-with-a-key)
206+
207+
```tsx
208+
// ❌ Dynamic list with duplicate or unstable identity
209+
items.map(item => <Row key={item.label} item={item} />);
210+
211+
// ✅ Stable data identity
212+
items.map(item => <Row key={item.id} item={item} />);
213+
214+
// ✅ Intentional subtree reset
215+
<Chat key={recipient.id} recipient={recipient} />;
216+
```

packages/plugin/README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ Design React APIs and abstractions in a React-like way.
3333

3434
### /react-hook-review
3535

36-
Review hooks against 30 design principles. Structured feedback with severity levels.
36+
Review hooks against 31 design principles. Structured feedback with severity levels.
3737

3838
- 14 coding principles (C1-C14): return values, SSR safety, TypeScript, cleanup, performance
39-
- 16 usage patterns (U1-U17, excluding U4): state design, effect usage, memoization, hook design
39+
- 17 usage patterns (U1-U18, excluding U4): state design, effect usage, memoization, hook design, key identity
4040
- Output: Great Work / Required Changes / Suggestions / Next Steps
4141

4242
### /react-hook-writing
@@ -50,14 +50,15 @@ Write hooks following design philosophy. Themed guide with code examples.
5050

5151
## Principles Overview
5252

53-
| Category | Count | Examples |
54-
| --------------------------- | ----- | --------------------------------------------------------------------------------------------- |
55-
| React design | 5 | Declarative interface, lifecycle respect, minimal surfaces, reliability, zero-dependency bias |
56-
| Coding (C1-C14) | 14 | Always return objects, SSR-safe init, no `any`, cleanup |
57-
| State Design (U1-U3, U5-U7) | 6 | Derive don't sync, useRef for non-rendered, discriminated unions |
58-
| Effect Usage (U8-U14) | 7 | Effects for sync only, no chains, key reset, async cleanup |
59-
| Memoization (U15-U16) | 2 | useMemo >= 1ms, useCallback + memo() only |
60-
| Hook Design (U17) | 1 | Extract reusable logic, not lifecycle wrappers |
53+
| Category | Count | Examples |
54+
| ---------------------------- | ----- | --------------------------------------------------------------------------------------------- |
55+
| React design | 5 | Declarative interface, lifecycle respect, minimal surfaces, reliability, zero-dependency bias |
56+
| Coding (C1-C14) | 14 | Always return objects, SSR-safe init, no `any`, cleanup |
57+
| State Design (U1-U3, U5-U7) | 6 | Derive don't sync, useRef for non-rendered, discriminated unions |
58+
| Effect Usage (U8-U14) | 7 | Effects for sync only, no chains, key reset, async cleanup |
59+
| Memoization (U15-U16) | 2 | useMemo >= 1ms, useCallback + memo() only |
60+
| Hook Design (U17) | 1 | Extract reusable logic, not lifecycle wrappers |
61+
| Identity and Rendering (U18) | 1 | Stable keys for list identity, intentional remounts, and rendering efficiency |
6162

6263
## Philosophy
6364

packages/plugin/skills/react-hook-review/SKILL.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ description: Review React hooks against design philosophy. Checks return values,
77

88
Review hooks against coding principles and usage patterns. Report findings by severity.
99

10+
When review involves lists, arrays of JSX, state reset/preservation, component identity, remounting, or rendering efficiency through React `key`, read [key-rendering.md](references/key-rendering.md) before writing findings.
11+
1012
Treat C1, C7, and C14 as opinionated conventions unless the target codebase explicitly adopts them. Report them as stronger findings when the repository standard is clear; otherwise phrase them as consistency recommendations.
1113

1214
## Coding Principles Checklist
@@ -86,12 +88,17 @@ Treat C1, C7, and C14 as opinionated conventions unless the target codebase expl
8688

8789
- **Extract logic, not lifecycle (U17)** — No `useMount`. Purpose-specific hooks only.
8890

91+
### Identity and Rendering
92+
93+
- **Stable keys (U18)** — Use stable, unique sibling keys from data for dynamic lists; use keys intentionally to preserve or reset subtree identity. See [key-rendering.md](references/key-rendering.md).
94+
8995
## Review Heuristics
9096

91-
- Flag React guidance from U1-U17 as behavior or maintainability issues first.
97+
- Flag React guidance from U1-U18 as behavior or maintainability issues first.
9298
- Flag C1 and C7 as API consistency issues unless the repo treats them as hard requirements.
9399
- Lower the severity of C14 unless debugging quality is materially affected.
94100
- When a hook mirrors props, chains effects, or hides lifecycle wrappers, explain the runtime consequence, not just the rule number.
101+
- For U18 findings, distinguish correctness from efficiency: duplicate/unstable keys can corrupt identity and state first; unnecessary remounts and DOM recreation are the performance consequence.
95102

96103
## Output Format
97104

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# React Key Rendering Review
2+
3+
Use this reference when reviewing code that renders lists, maps data to JSX, resets child state, preserves child state, or tries to improve rendering efficiency with `key`.
4+
5+
Primary references:
6+
7+
- React docs: Rendering Lists — https://react.dev/learn/rendering-lists
8+
- React docs: Preserving and Resetting State — https://react.dev/learn/preserving-and-resetting-state
9+
10+
## Review Intent
11+
12+
`key` is React's identity hint for children under the same parent. Review `key` usage as an identity and correctness concern first, and as a rendering efficiency concern second.
13+
14+
A good review explains what React will be able to reuse, what it will remount, and whether that behavior matches the domain identity of the UI.
15+
16+
## Required Checks
17+
18+
1. Dynamic sibling lists must have stable, unique keys.
19+
20+
- Prefer IDs that already exist in the data.
21+
- For locally created persisted items, generate the ID when creating the item, not during render.
22+
- Keys only need to be unique among siblings, not globally.
23+
24+
2. Do not use unstable keys for dynamic lists.
25+
26+
- Avoid `key={Math.random()}`, `key={Date.now()}`, or keys derived from values that change every render.
27+
- Avoid array index keys when items can be inserted, deleted, sorted, filtered, or reordered.
28+
- Index keys are acceptable only for static lists whose order and membership cannot change.
29+
30+
3. Duplicated sibling keys are a correctness bug.
31+
32+
- React cannot reliably match children when two siblings claim the same identity.
33+
- In dynamic updates, duplicate keys can make one previous child untracked while another child with the same key is reused, producing confusing UI such as stale rows, unexpected disappearances, or visually duplicated items.
34+
- If the code uses a property named like `uniqueKey`, verify it is actually unique for siblings in the rendered list.
35+
36+
4. Fragments in lists need explicit keys.
37+
38+
- Short fragments (`<>...</>`) cannot receive keys.
39+
- If each item renders multiple sibling nodes, use `<Fragment key={item.id}>...</Fragment>` or a real wrapper element when the wrapper is semantically useful.
40+
- A keyed fragment can give React a child identity, but it does not create a DOM parent. If the UI relies on a DOM boundary for layout, styling, or containment, use a real element.
41+
42+
5. Use `key` to intentionally reset state instead of effect-based clearing.
43+
- When a child subtree represents a different conceptual entity at the same JSX position, pass a different key, such as `<Chat key={recipient.id} recipient={recipient} />`.
44+
- This remounts the subtree, recreates DOM nodes, and clears local state. Use it when that reset is desired.
45+
- Do not use a changing key to paper over state bugs or force refreshes; it discards state and work.
46+
47+
## Rendering Efficiency Guidance
48+
49+
Stable keys let React match the same item across renders even when items move, are inserted, or are deleted. This preserves component state and lets React update the existing DOM/component instance instead of recreating unrelated rows.
50+
51+
Unstable keys defeat matching. If every render produces new keys, React treats the children as new identities, recreates components and DOM, and loses local state such as input text or focus. This is both slower and behaviorally risky.
52+
53+
Changing a key is the right tool for a deliberate remount. It can simplify code by replacing cleanup effects or manual reset effects, but it is not a generic optimization. Prefer preserving keys for stable entities and changing keys only when the domain identity changes.
54+
55+
## Review Wording
56+
57+
For required changes:
58+
59+
```md
60+
**[U18] Use stable keys for dynamic list identity**
61+
62+
- Current: `key={item.name}`
63+
- Suggested: `key={item.id}`
64+
- Why: React uses `key` to match siblings across insert/delete/reorder updates. `name` is not unique here, so one row can be reused for the wrong item and another can be remounted.
65+
```
66+
67+
For intentional reset suggestions:
68+
69+
```md
70+
**[U10/U18] Reset child state with a domain key**
71+
72+
- Current: `useEffect(() => setDraft(''), [recipient.id])`
73+
- Suggested: `<Chat key={recipient.id} recipient={recipient} />`
74+
- Why: The chat draft belongs to a recipient-specific subtree. A recipient key tells React to remount that subtree when the identity changes.
75+
```
76+
77+
For non-blocking efficiency notes:
78+
79+
```md
80+
Consider replacing `key={index}` with a stable item ID if this list can be filtered or reordered later. Stable keys let React preserve row identity and avoid remounting unrelated items.
81+
```

0 commit comments

Comments
 (0)