Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/tough-peas-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-simplikit': patch
---

add new skill for react key rendering pattern
20 changes: 11 additions & 9 deletions docs/hook-design-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,15 @@ function useFetch<T>(url: string) { const res = await axios.get(url); ... }

> Separate document: [react-hook-usage-patterns.md](./react-hook-usage-patterns.md)

16 patterns based on React official docs (react.dev), with source URLs and quotes (U1-U17, U4 removed):
17 patterns based on React official docs (react.dev), with source URLs and quotes (U1-U18, U4 removed):

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

---

Expand All @@ -301,7 +302,8 @@ packages/plugin/ (planned)
├── .codex-plugin/plugin.json
├── principles/ ← Shared principles single source
├── skills/
│ ├── react-hook-review/SKILL.md ← C1-C14 + U1-U17 checklist
│ ├── react-hook-review/SKILL.md ← C1-C14 + U1-U18 checklist
│ ├── react-hook-review/references/key-rendering.md
│ └── react-hook-writing/
│ ├── SKILL.md ← Themed guide
│ └── references/patterns.md ← 3 hook implementations
Expand Down Expand Up @@ -342,7 +344,7 @@ packages/plugin/ (planned)
| Phase | Content | Output |
| ----- | ----------------------------------------- | ------------------------------ |
| 1 | Directory + plugin.json + README | `packages/plugin/` structure |
| 2 | react-hook-review SKILL.md | C1-C14 + U1-U17 checklist |
| 2 | react-hook-review SKILL.md | C1-C14 + U1-U18 checklist |
| 3 | react-hook-writing SKILL.md + patterns.md | Themed guide + 3 hook examples |
| 4 | Generalization validation (grep) | 0 project references |
| 5 | Plugin validate + local test | Working confirmation |
Expand Down
20 changes: 11 additions & 9 deletions docs/ko/hook-design-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,15 @@ function useFetch<T>(url: string) { const res = await axios.get(url); ... }

> 별도 문서: [react-hook-usage-patterns.md](./react-hook-usage-patterns.md)

React 공식 문서(react.dev) 기반 16개 패턴 (U1-U17, U4 제거):
React 공식 문서(react.dev) 기반 17개 패턴 (U1-U18, U4 제거):

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

---

Expand All @@ -291,7 +292,8 @@ packages/plugin/ (planned)
├── .codex-plugin/plugin.json
├── principles/ ← 공통 원칙 Single Source
├── skills/
│ ├── react-hook-review/SKILL.md ← C1-C14 + U1-U17 체크리스트
│ ├── react-hook-review/SKILL.md ← C1-C14 + U1-U18 체크리스트
│ ├── react-hook-review/references/key-rendering.md
│ └── react-hook-writing/
│ ├── SKILL.md ← 테마별 가이드
│ └── references/patterns.md ← 구현 예시 3개
Expand Down Expand Up @@ -332,7 +334,7 @@ packages/plugin/ (planned)
| Phase | 내용 | 산출물 |
| ----- | ----------------------------------------- | --------------------------- |
| 1 | 디렉토리 + plugin.json + README | `packages/plugin/` 구조 |
| 2 | react-hook-review SKILL.md | C1-C14 + U1-U17 체크리스트 |
| 2 | react-hook-review SKILL.md | C1-C14 + U1-U18 체크리스트 |
| 3 | react-hook-writing SKILL.md + patterns.md | 테마별 가이드 + 3개 훅 예시 |
| 4 | 일반화 검증 (grep) | 프로젝트 참조 0건 |
| 5 | 플러그인 validate + 로컬 테스트 | 동작 확인 |
Expand Down
24 changes: 23 additions & 1 deletion docs/ko/react-hook-usage-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
> 출처: React 공식 문서 (react.dev)
> 관련: [Hook Design Principles](./hook-design-principles.md)

코딩 스타일이 아닌 **hooks를 올바르게 사용하는 패턴**. 16개 원칙 (U1-U17, U4 제거).
코딩 스타일이 아닌 **hooks를 올바르게 사용하는 패턴**. 17개 원칙 (U1-U18, U4 제거).

---

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

lifecycle wrapper(`useMount`, `useEffectOnce`) 금지. 구체적 동기화 목적 훅(`useWindowSize`, `useOnlineStatus`)만.
추출 기준: 동일 state+effect 패턴이 2개+ 컴포넌트에서 반복되는지?

---

## Identity and Rendering (1개)

### U18. 리스트와 서브트리 identity에는 안정적인 key 사용

동적인 리스트에는 데이터에서 온 안정적이고 sibling 사이에서 유일한 key를 사용한다. key는 insert/delete/reorder 업데이트에서 같은 아이템을 다시 매칭해 state를 보존하고 불필요한 DOM/component 재생성을 줄인다. 반대로 서브트리를 리셋해야 할 때만 의도적으로 key를 바꾼다.

> 📖 [Rendering Lists — Keeping list items in order with key](https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key)
> 📖 [Preserving and Resetting State — Resetting state with a key](https://react.dev/learn/preserving-and-resetting-state#option-2-resetting-state-with-a-key)

```tsx
// ❌ 중복되거나 불안정한 identity
items.map(item => <Row key={item.label} item={item} />);

// ✅ 데이터 기반의 안정적인 identity
items.map(item => <Row key={item.id} item={item} />);

// ✅ 의도적인 서브트리 리셋
<Chat key={recipient.id} recipient={recipient} />;
```
24 changes: 23 additions & 1 deletion docs/react-hook-usage-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
> Related: [Hook Design Principles](./hook-design-principles.md)
> Korean version: [ko/react-hook-usage-patterns.md](./ko/react-hook-usage-patterns.md)

Patterns for **correctly using hooks** — not coding style, but React-specific best practices. 16 principles (U1-U17, U4 removed).
Patterns for **correctly using hooks** — not coding style, but React-specific best practices. 17 principles (U1-U18, U4 removed).

---

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

> 📖 [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) > _"Custom Hooks let you share stateful logic, not state itself."_

---

## Identity and Rendering (1)

### U18. Use Stable Keys for List and Subtree Identity

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.

> 📖 [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"_
> 📖 [Preserving and Resetting State — Resetting state with a key](https://react.dev/learn/preserving-and-resetting-state#option-2-resetting-state-with-a-key)

```tsx
// ❌ Dynamic list with duplicate or unstable identity
items.map(item => <Row key={item.label} item={item} />);

// ✅ Stable data identity
items.map(item => <Row key={item.id} item={item} />);

// ✅ Intentional subtree reset
<Chat key={recipient.id} recipient={recipient} />;
```
21 changes: 11 additions & 10 deletions packages/plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ Design React APIs and abstractions in a React-like way.

### /react-hook-review

Review hooks against 30 design principles. Structured feedback with severity levels.
Review hooks against 31 design principles. Structured feedback with severity levels.

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

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

## Principles Overview

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

## Philosophy

Expand Down
9 changes: 8 additions & 1 deletion packages/plugin/skills/react-hook-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ description: Review React hooks against design philosophy. Checks return values,

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

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.

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.

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

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

### Identity and Rendering

- **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).

## Review Heuristics

- Flag React guidance from U1-U17 as behavior or maintainability issues first.
- Flag React guidance from U1-U18 as behavior or maintainability issues first.
- Flag C1 and C7 as API consistency issues unless the repo treats them as hard requirements.
- Lower the severity of C14 unless debugging quality is materially affected.
- When a hook mirrors props, chains effects, or hides lifecycle wrappers, explain the runtime consequence, not just the rule number.
- 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.

## Output Format

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# React Key Rendering Review

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`.

Primary references:

- React docs: Rendering Lists — https://react.dev/learn/rendering-lists
- React docs: Preserving and Resetting State — https://react.dev/learn/preserving-and-resetting-state

## Review Intent

`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.

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.

## Required Checks

1. Dynamic sibling lists must have stable, unique keys.

- Prefer IDs that already exist in the data.
- For locally created persisted items, generate the ID when creating the item, not during render.
- Keys only need to be unique among siblings, not globally.

2. Do not use unstable keys for dynamic lists.

- Avoid `key={Math.random()}`, `key={Date.now()}`, or keys derived from values that change every render.
- Avoid array index keys when items can be inserted, deleted, sorted, filtered, or reordered.
- Index keys are acceptable only for static lists whose order and membership cannot change.

3. Duplicated sibling keys are a correctness bug.

- React cannot reliably match children when two siblings claim the same identity.
- 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.
- If the code uses a property named like `uniqueKey`, verify it is actually unique for siblings in the rendered list.

4. Fragments in lists need explicit keys.

- Short fragments (`<>...</>`) cannot receive keys.
- If each item renders multiple sibling nodes, use `<Fragment key={item.id}>...</Fragment>` or a real wrapper element when the wrapper is semantically useful.
- 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.

5. Use `key` to intentionally reset state instead of effect-based clearing.
- 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} />`.
- This remounts the subtree, recreates DOM nodes, and clears local state. Use it when that reset is desired.
- Do not use a changing key to paper over state bugs or force refreshes; it discards state and work.

## Rendering Efficiency Guidance

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.

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.

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.

## Review Wording

For required changes:

```md
**[U18] Use stable keys for dynamic list identity**

- Current: `key={item.name}`
- Suggested: `key={item.id}`
- 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.
```

For intentional reset suggestions:

```md
**[U10/U18] Reset child state with a domain key**

- Current: `useEffect(() => setDraft(''), [recipient.id])`
- Suggested: `<Chat key={recipient.id} recipient={recipient} />`
- Why: The chat draft belongs to a recipient-specific subtree. A recipient key tells React to remount that subtree when the identity changes.
```

For non-blocking efficiency notes:

```md
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.
```
Loading