Skip to content

Commit c92a1c4

Browse files
orionmizclaude
andauthored
chore: add React code review skill for agent-based guidance (#687)
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent c740c5e commit c92a1c4

28 files changed

Lines changed: 1381 additions & 0 deletions
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
name: review-react
3+
description: >
4+
React code review guidelines covering Rules of React, re-render optimization,
5+
rendering performance, and advanced patterns. Activates when writing, reviewing,
6+
or refactoring React components, hooks, or state management code.
7+
---
8+
9+
# React Code Review Guidelines
10+
11+
Performance optimization and correctness guide for React applications. Contains 23 rules across 4 categories, prioritized by impact.
12+
13+
## When to Apply
14+
15+
Reference these guidelines when:
16+
- Writing or reviewing React components and hooks
17+
- Optimizing re-render performance
18+
- Refactoring state management or effect logic
19+
- Reviewing pull requests that touch React code
20+
21+
## Rule Categories by Priority
22+
23+
| Priority | Category | Impact | Prefix | Rules |
24+
|----------|----------|--------|--------|-------|
25+
| 1 | Rules of React | CRITICAL | `react-rules-` | 3 |
26+
| 2 | Re-render Optimization | MEDIUM | `rerender-` | 13 |
27+
| 3 | Rendering Performance | MEDIUM | `rendering-` | 5 |
28+
| 4 | Advanced Patterns | LOW | `advanced-` | 2 |
29+
30+
## Quick Reference
31+
32+
### 1. Rules of React (CRITICAL)
33+
34+
- `react-rules-purity` - Components and Hooks must be pure; no side effects during render
35+
- `react-rules-hooks` - Only call Hooks at the top level and from React functions
36+
- `react-rules-calling` - Never call components as functions or pass Hooks as values
37+
38+
### 2. Re-render Optimization (MEDIUM)
39+
40+
- `rerender-no-inline-components` - Never define components inside other components
41+
- `rerender-derived-state-no-effect` - Derive state during render, not in effects
42+
- `rerender-memo` - Extract memoized child components to avoid re-renders
43+
- `rerender-memo-with-default-value` - Hoist default non-primitive props outside memo
44+
- `rerender-simple-expression-in-memo` - Don't useMemo for simple primitive expressions
45+
- `rerender-defer-reads` - Don't subscribe to state only used in callbacks
46+
- `rerender-dependencies` - Use primitive values in effect dependencies
47+
- `rerender-derived-state` - Subscribe to derived booleans, not raw objects
48+
- `rerender-functional-setstate` - Use functional setState for stable callbacks
49+
- `rerender-lazy-state-init` - Pass initializer function to useState for expensive values
50+
- `rerender-move-effect-to-event` - Move interaction logic from effects to event handlers
51+
- `rerender-transitions` - Use startTransition for non-urgent state updates
52+
- `rerender-use-ref-transient-values` - Use refs for frequently-changing transient values
53+
54+
### 3. Rendering Performance (MEDIUM)
55+
56+
- `rendering-hoist-jsx` - Hoist static JSX outside component functions
57+
- `rendering-conditional-render` - Use ternary operator instead of && for conditional rendering
58+
- `rendering-usetransition-loading` - Prefer useTransition over manual loading state
59+
- `rendering-content-visibility` - Use CSS content-visibility: auto for long lists
60+
- `rendering-activity` - Use Activity component for preserving hidden UI state
61+
62+
### 4. Advanced Patterns (LOW)
63+
64+
- `advanced-event-handler-refs` - Store latest event handlers in refs for stable callbacks
65+
- `advanced-init-once` - Initialize app-level singletons once, not per mount
66+
67+
## How to Use
68+
69+
Read individual rule files for detailed explanations and code examples:
70+
71+
```
72+
rules/react-rules-purity.md
73+
rules/rerender-no-inline-components.md
74+
```
75+
76+
Each rule file contains:
77+
- Brief explanation of why it matters
78+
- Incorrect code example
79+
- Correct code example
80+
- Reference links
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Sections
2+
3+
This file defines all sections, their ordering, impact levels, and descriptions.
4+
The section ID (in parentheses) is the filename prefix used to group rules.
5+
6+
---
7+
8+
## 1. Rules of React (react-rules)
9+
10+
**Impact:** CRITICAL
11+
**Description:** Fundamental rules from react.dev that ensure correctness. Components must be pure, Hooks must follow call rules, and components must not be called as functions.
12+
13+
## 2. Re-render Optimization (rerender)
14+
15+
**Impact:** MEDIUM
16+
**Description:** Patterns to minimize unnecessary re-renders: proper memoization, derived state, functional setState, and effect dependency management.
17+
18+
## 3. Rendering Performance (rendering)
19+
20+
**Impact:** MEDIUM
21+
**Description:** Techniques to optimize what and how React renders: hoisting static JSX, conditional rendering patterns, content-visibility, and transitions.
22+
23+
## 4. Advanced Patterns (advanced)
24+
25+
**Impact:** LOW
26+
**Description:** Specialized techniques for edge cases: storing handlers in refs for stable callbacks and one-time initialization patterns.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
title: Rule Title Here
3+
impact: MEDIUM
4+
impactDescription: Optional description of impact (e.g., "20-50% improvement")
5+
tags: tag1, tag2
6+
---
7+
8+
## Rule Title Here
9+
10+
**Impact: MEDIUM (optional impact description)**
11+
12+
Brief explanation of the rule and why it matters. This should be clear and concise, explaining the performance implications.
13+
14+
**Incorrect (description of what's wrong):**
15+
16+
```typescript
17+
// Bad code example here
18+
const bad = example()
19+
```
20+
21+
**Correct (description of what's right):**
22+
23+
```typescript
24+
// Good code example here
25+
const good = example()
26+
```
27+
28+
Reference: [Link to documentation or resource](https://example.com)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
---
2+
title: Store Event Handlers in Refs
3+
impact: LOW
4+
impactDescription: stable subscriptions
5+
tags: advanced, hooks, refs, event-handlers, optimization
6+
---
7+
8+
## Store Event Handlers in Refs
9+
10+
Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes.
11+
12+
**Incorrect (re-subscribes on every render):**
13+
14+
```tsx
15+
function useWindowEvent(event: string, handler: (e) => void) {
16+
useEffect(() => {
17+
window.addEventListener(event, handler)
18+
return () => window.removeEventListener(event, handler)
19+
}, [event, handler])
20+
}
21+
```
22+
23+
**Correct (stable subscription):**
24+
25+
```tsx
26+
function useWindowEvent(event: string, handler: (e) => void) {
27+
const handlerRef = useRef(handler)
28+
useEffect(() => {
29+
handlerRef.current = handler
30+
}, [handler])
31+
32+
useEffect(() => {
33+
const listener = (e) => handlerRef.current(e)
34+
window.addEventListener(event, listener)
35+
return () => window.removeEventListener(event, listener)
36+
}, [event])
37+
}
38+
```
39+
40+
**Alternative: use `useEffectEvent` if you're on latest React:**
41+
42+
```tsx
43+
import { useEffectEvent } from 'react'
44+
45+
function useWindowEvent(event: string, handler: (e) => void) {
46+
const onEvent = useEffectEvent(handler)
47+
48+
useEffect(() => {
49+
window.addEventListener(event, onEvent)
50+
return () => window.removeEventListener(event, onEvent)
51+
}, [event])
52+
}
53+
```
54+
55+
`useEffectEvent` provides a cleaner API for the same pattern: it creates a stable function reference that always calls the latest version of the handler.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: Initialize App Once, Not Per Mount
3+
impact: LOW-MEDIUM
4+
impactDescription: avoids duplicate init in development
5+
tags: initialization, useEffect, app-startup, side-effects
6+
---
7+
8+
## Initialize App Once, Not Per Mount
9+
10+
Do not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard or top-level init in the entry module instead.
11+
12+
**Incorrect (runs twice in dev, re-runs on remount):**
13+
14+
```tsx
15+
function Comp() {
16+
useEffect(() => {
17+
loadFromStorage()
18+
checkAuthToken()
19+
}, [])
20+
21+
// ...
22+
}
23+
```
24+
25+
**Correct (once per app load):**
26+
27+
```tsx
28+
let didInit = false
29+
30+
function Comp() {
31+
useEffect(() => {
32+
if (didInit) return
33+
didInit = true
34+
loadFromStorage()
35+
checkAuthToken()
36+
}, [])
37+
38+
// ...
39+
}
40+
```
41+
42+
Reference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
title: React Calls Components and Hooks
3+
impact: CRITICAL
4+
impactDescription: breaks React's rendering model and optimization
5+
tags: react-rules, components, hooks, calling-convention
6+
---
7+
8+
## React Calls Components and Hooks
9+
10+
**Impact: CRITICAL (breaks React's rendering model and optimization)**
11+
12+
React must control when components render and hooks execute. Calling them directly bypasses reconciliation, state management, and optimization.
13+
14+
### Rule 1: Never call component functions directly
15+
16+
**Incorrect (calling component as function):**
17+
18+
```tsx
19+
function Parent() {
20+
// This bypasses React's rendering, no proper lifecycle or state isolation
21+
return <div>{Profile({ name: 'Alice' })}</div>
22+
}
23+
```
24+
25+
**Correct (use JSX):**
26+
27+
```tsx
28+
function Parent() {
29+
return <div><Profile name="Alice" /></div>
30+
}
31+
```
32+
33+
Calling a component as a function makes React treat it as part of the parent's render. This means:
34+
- No separate fiber node for reconciliation
35+
- State and effects are tied to the parent
36+
- Keys and refs don't work as expected
37+
38+
### Rule 2: Never pass Hooks as regular values
39+
40+
**Incorrect (passing hook as prop):**
41+
42+
```tsx
43+
function ChatRoom({ useStatus }) {
44+
const status = useStatus() // Hook passed as value
45+
return <p>{status}</p>
46+
}
47+
48+
<ChatRoom useStatus={useOnlineStatus} />
49+
```
50+
51+
**Correct (call hook directly, pass result as prop):**
52+
53+
```tsx
54+
function ChatRoom({ status }) {
55+
return <p>{status}</p>
56+
}
57+
58+
function ChatRoomWrapper() {
59+
const status = useOnlineStatus()
60+
return <ChatRoom status={status} />
61+
}
62+
```
63+
64+
Passing hooks as values makes them opaque to React's static analysis, breaks the Rules of Hooks, and prevents the compiler from optimizing correctly.
65+
66+
Reference: [React calls Components and Hooks](https://react.dev/reference/rules/react-calls-components-and-hooks)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
title: Rules of Hooks
3+
impact: CRITICAL
4+
impactDescription: violating causes runtime errors and broken state
5+
tags: react-rules, hooks, top-level
6+
---
7+
8+
## Rules of Hooks
9+
10+
**Impact: CRITICAL (violating causes runtime errors and broken state)**
11+
12+
Hooks rely on a stable call order. Calling them conditionally or in loops breaks React's ability to track state correctly.
13+
14+
### Rule 1: Only call Hooks at the top level
15+
16+
**Incorrect (hook inside condition):**
17+
18+
```tsx
19+
function Form({ showName }) {
20+
if (showName) {
21+
const [name, setName] = useState('') // Conditional hook call
22+
}
23+
const [email, setEmail] = useState('')
24+
return <input value={email} onChange={e => setEmail(e.target.value)} />
25+
}
26+
```
27+
28+
**Correct (always call hooks, conditionally use values):**
29+
30+
```tsx
31+
function Form({ showName }) {
32+
const [name, setName] = useState('')
33+
const [email, setEmail] = useState('')
34+
return (
35+
<>
36+
{showName && <input value={name} onChange={e => setName(e.target.value)} />}
37+
<input value={email} onChange={e => setEmail(e.target.value)} />
38+
</>
39+
)
40+
}
41+
```
42+
43+
**Incorrect (hook inside loop):**
44+
45+
```tsx
46+
function Filters({ filters }) {
47+
const values = []
48+
for (const f of filters) {
49+
values.push(useState(f.default)) // Hook in loop
50+
}
51+
return <>{/* ... */}</>
52+
}
53+
```
54+
55+
**Correct (extract to child component or use single state):**
56+
57+
```tsx
58+
function Filters({ filters }) {
59+
const [values, setValues] = useState(() =>
60+
Object.fromEntries(filters.map(f => [f.id, f.default]))
61+
)
62+
return <>{/* ... */}</>
63+
}
64+
```
65+
66+
### Rule 2: Only call Hooks from React functions
67+
68+
**Incorrect (hook in regular function):**
69+
70+
```tsx
71+
function getUser() {
72+
const [user, setUser] = useState(null) // Not a React component
73+
return user
74+
}
75+
```
76+
77+
**Correct (hook in component or custom hook):**
78+
79+
```tsx
80+
function useUser() {
81+
const [user, setUser] = useState(null)
82+
return user
83+
}
84+
85+
function Profile() {
86+
const user = useUser()
87+
return <p>{user?.name}</p>
88+
}
89+
```
90+
91+
Reference: [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks)

0 commit comments

Comments
 (0)