Skip to content

Commit 87e45a0

Browse files
authored
update 'component-guide/SKILL.md' and add 'react-rerender-mental-models (#1995)
1 parent dd795be commit 87e45a0

File tree

10 files changed

+844
-1
lines changed

10 files changed

+844
-1
lines changed

.claude/skills/component-guide/SKILL.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ export const ComponentName = ({ propName }: ComponentNameProps) => {
102102
### Do
103103
- Use design system components for all standard UI elements
104104
- Use SCSS with BEM-ish class naming (`ComponentName__element--modifier`)
105-
- Use `pxToRem()` for spacing values in SCSS
105+
- Use `pxToRem()` for spacing values in SCSS (skip for trivial values like
106+
`1px` or `-1px` — e.g., borders, outlines, offsets — where rem scaling is
107+
not meaningful)
106108
- Use SDS CSS custom properties for colors/fonts/gaps: `var(--sds-clr-gray-06)`,
107109
`var(--sds-ff-monospace)`, `var(--sds-gap-md)`
108110
- Use `data-*` attributes for state-driven styling instead of modifier classes:
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
name: react-rerender-mental-models
3+
description: Correct mental models for React re-renders and memoization. Use this skill when writing, reviewing, or optimizing React components to avoid common misconceptions about performance. Debunks the myth that "props cause re-renders" and teaches when memoization actually helps.
4+
license: MIT
5+
metadata:
6+
author: freighter
7+
version: "1.0.0"
8+
---
9+
10+
# React Re-render Mental Models
11+
12+
A practical guide to understanding how React re-renders actually work, debunking common misconceptions, and knowing when optimization is truly needed.
13+
14+
## Core Philosophy
15+
16+
> "Trust React to be fast. Write simple code. Measure when something actually feels slow. Optimize based on data, not fear."
17+
18+
## When to Apply
19+
20+
Reference these guidelines when:
21+
- Writing new React components
22+
- Reviewing code for performance issues
23+
- Deciding whether to add React.memo, useCallback, or useMemo
24+
- Debugging perceived "slowness" in React apps
25+
- Refactoring component architecture
26+
27+
## Rule Categories
28+
29+
| Category | Prefix | Description |
30+
|----------|--------|-------------|
31+
| Mental Models | `mental-model-` | Correct understanding of how React works |
32+
| Anti-Patterns | `anti-pattern-` | Common mistakes to avoid |
33+
| When to Memoize | `when-to-memo-` | Profile-driven optimization guidance |
34+
| Architecture | `architecture-` | Component structure patterns |
35+
36+
## Quick Reference
37+
38+
### Mental Models (Foundational)
39+
40+
- `mental-model-parent-triggers` - Props don't trigger re-renders; parent re-renders do
41+
- `mental-model-render-vs-commit` - Render ≠ Commit; renders are cheap, commits touch DOM
42+
43+
### Anti-Patterns (Avoid These)
44+
45+
- `anti-pattern-premature-memo` - Don't memoize without profiling first
46+
- `anti-pattern-memoization-trap` - Real bottlenecks are often not re-renders
47+
48+
### When to Memoize (Use Sparingly)
49+
50+
- `when-to-memo-context-values` - Stabilize context object references
51+
- `when-to-usecallback` - Only needed when child is already memoized
52+
53+
### Architecture (Prefer These)
54+
55+
- `architecture-local-state` - Keep state close to where it's used
56+
- `architecture-composition` - Component structure prevents re-renders naturally
57+
58+
## What Actually Triggers Re-renders
59+
60+
1. **Component's own state changes** (useState, useReducer)
61+
2. **Parent component re-renders** (cascades to all children)
62+
3. **Context value changes** (for consumers of that context)
63+
64+
**Props are NOT on this list.** Props are inputs to a render that's already happening—they don't schedule one.
65+
66+
## The Performance Debugging Flow
67+
68+
```
69+
1. Something feels slow
70+
2. Open React DevTools Profiler
71+
3. Record the interaction
72+
4. Look for components with 50ms+ render times
73+
5. If found → consider memoization
74+
6. If not found → the problem is elsewhere:
75+
- API calls firing too often
76+
- Unoptimized images
77+
- Too many DOM nodes
78+
- CSS animations triggering layout
79+
```
80+
81+
## React 19 Compiler
82+
83+
This project runs React 19. The **React Compiler** (formerly React Forget) is
84+
an opt-in build-time tool that statically analyzes components and automatically
85+
inserts the equivalent of `useMemo`, `useCallback`, and `React.memo`. It is
86+
**not enabled by default** — it requires `babel-plugin-react-compiler` (or the
87+
SWC equivalent) to be installed and configured.
88+
89+
- If the compiler is **enabled in this project**: skip manual memoization. The
90+
compiler tracks data flow within each component and caches results at a more
91+
granular level than hand-written hooks. If the compiler opts out a specific
92+
component (e.g. due to side effects during render), fix the component rather
93+
than adding manual memo.
94+
- If the compiler is **not enabled** (current default): follow the rules below,
95+
but prefer architecture fixes (local state, composition) over adding
96+
memo/useCallback.
97+
98+
Regardless of compiler status, the mental models and architecture rules in this
99+
skill still apply — composition and local state are always better than relying
100+
on memoization (auto or manual). Re-render triggers remain the same either way.
101+
102+
## How to Use
103+
104+
Read individual rule files for detailed explanations and code examples:
105+
106+
```
107+
rules/mental-model-parent-triggers.md
108+
rules/anti-pattern-premature-memo.md
109+
rules/architecture-local-state.md
110+
```
111+
112+
Each rule file contains:
113+
- Brief explanation of why it matters
114+
- Incorrect code example with explanation
115+
- Correct code example with explanation
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
title: The Memoization Trap
3+
impact: HIGH
4+
impactDescription: identifies real performance bottlenecks
5+
tags: anti-pattern, debugging, bottlenecks, profiling
6+
---
7+
8+
## The Memoization Trap
9+
10+
You see slowness, assume it's re-renders, add memoization, feel productive—meanwhile the real issue is something else entirely.
11+
12+
### Real-World Example
13+
14+
A search input feels laggy. Every keystroke has a noticeable delay.
15+
16+
```tsx
17+
// The "obvious" fix that doesn't work
18+
const ProductList = () => {
19+
const [search, setSearch] = useState("");
20+
const [products, setProducts] = useState([]);
21+
22+
// ❌ Added useMemo - still laggy
23+
const filteredProducts = useMemo(
24+
() => products.filter((p) =>
25+
p.name.toLowerCase().includes(search.toLowerCase())
26+
),
27+
[products, search]
28+
);
29+
30+
// ❌ Added useCallback - still laggy
31+
const handleChange = useCallback((value) => {
32+
setSearch(value);
33+
}, []);
34+
35+
return (
36+
<>
37+
<SearchInput value={search} onChange={handleChange} />
38+
{filteredProducts.map((product) => (
39+
<MemoizedProductCard key={product.id} product={product} />
40+
))}
41+
</>
42+
);
43+
};
44+
```
45+
46+
### The Actual Problem
47+
48+
After profiling:
49+
- The `.filter()` call: ~0.3ms on 200 products
50+
- Re-rendering ProductCards: ~8ms total
51+
- **A useEffect hitting analytics API on every keystroke: 200ms+ blocking**
52+
53+
```tsx
54+
// ✅ The real fix
55+
useEffect(() => {
56+
// Debounce the analytics call
57+
const timer = setTimeout(() => {
58+
analytics.track("search", { query: search });
59+
}, 300);
60+
return () => clearTimeout(timer);
61+
}, [search]);
62+
```
63+
64+
### Common Real Bottlenecks (Not Re-renders)
65+
66+
| Symptom | Likely Cause | Fix |
67+
|---------|--------------|-----|
68+
| Laggy input | API calls on every keystroke | Debounce |
69+
| Slow initial load | Large unoptimized images | Lazy load, compress |
70+
| Scroll jank | Too many DOM nodes (1000+) | Virtualization |
71+
| Animation stutter | CSS triggering layout recalc | Use transform/opacity only |
72+
| Memory issues | Event listeners not cleaned up | Proper useEffect cleanup |
73+
74+
### Key Takeaway
75+
76+
If the React DevTools Profiler shows render times under 16ms, re-renders aren't
77+
the problem — look at network calls, DOM node count, and CSS layout thrashing
78+
instead.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
title: Don't Memoize Without Profiling
3+
impact: HIGH
4+
impactDescription: prevents unnecessary complexity
5+
tags: anti-pattern, memo, useCallback, useMemo, profiling
6+
---
7+
8+
## Don't Memoize Without Profiling
9+
10+
Adding `React.memo`, `useCallback`, and `useMemo` without profiling first is premature optimization. For cheap components (most of them), the memo check itself costs more than just re-rendering.
11+
12+
### The Problem
13+
14+
```tsx
15+
// ❌ Incorrect: Memoizing without knowing if it helps
16+
const ProductCard = React.memo(({ product, onSelect }) => {
17+
return (
18+
<div onClick={() => onSelect(product.id)}>
19+
<h3>{product.name}</h3>
20+
<p>${product.price}</p>
21+
</div>
22+
);
23+
});
24+
25+
const ProductList = ({ products }) => {
26+
// ❌ useCallback "just in case"
27+
const handleSelect = useCallback((id) => {
28+
console.log("Selected:", id);
29+
}, []);
30+
31+
return products.map((p) => (
32+
<ProductCard key={p.id} product={p} onSelect={handleSelect} />
33+
));
34+
};
35+
```
36+
37+
This adds complexity with no proven benefit. The memo check happens every render, comparing props that probably haven't changed.
38+
39+
### The Correct Approach
40+
41+
**Step 1: Write simple code first**
42+
43+
```tsx
44+
// ✅ Correct: Simple, readable, no premature optimization
45+
const ProductCard = ({ product, onSelect }) => {
46+
return (
47+
<div onClick={() => onSelect(product.id)}>
48+
<h3>{product.name}</h3>
49+
<p>${product.price}</p>
50+
</div>
51+
);
52+
};
53+
54+
const ProductList = ({ products }) => {
55+
const handleSelect = (id) => {
56+
console.log("Selected:", id);
57+
};
58+
59+
return products.map((p) => (
60+
<ProductCard key={p.id} product={p} onSelect={handleSelect} />
61+
));
62+
};
63+
```
64+
65+
**Step 2: Profile when something feels slow** — only add memoization for
66+
components with **50ms+ render times** that re-render frequently with unchanged
67+
props.
68+
69+
### The Real Cost of Premature Memoization
70+
71+
- **Cognitive overhead**: More code to read and maintain
72+
- **Hidden bugs**: Stale closures from incorrect dependency arrays
73+
- **False confidence**: "I optimized it" when the real issue is elsewhere
74+
- **Memo overhead**: The comparison check runs every render
75+
76+
> **Note:** If the React Compiler is enabled, it handles memoization
77+
> automatically — manual `memo`/`useMemo`/`useCallback` becomes unnecessary for
78+
> most components.

0 commit comments

Comments
 (0)