@codihaus/claude-skills 1.6.18 → 1.6.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -571,9 +571,30 @@ When reviewing React code:
571
571
  - useDeferredValue (defer updates for performance)
572
572
  - useSyncExternalStore (external store integration)
573
573
 
574
+ ## Advanced Performance Patterns
575
+
576
+ **See `references/performance.md` for comprehensive optimization guide covering**:
577
+ - Re-render optimization (functional setState, lazy initialization, transitions)
578
+ - Rendering performance (hoisting, content-visibility, hydration)
579
+ - Client-side optimization (passive listeners, storage caching)
580
+ - JavaScript performance (index maps, Set/Map, toSorted)
581
+ - Advanced patterns (useLatest, event handler refs)
582
+
583
+ **Key takeaways**:
584
+ - Use functional setState to prevent stale closures
585
+ - Lazy initialize expensive state with function
586
+ - Mark non-urgent updates with startTransition
587
+ - Hoist static JSX outside render
588
+ - Use content-visibility for long lists
589
+ - Cache storage API calls in memory
590
+ - Build index Maps for repeated lookups
591
+ - Use toSorted() instead of sort() for immutability
592
+
574
593
  ## Resources
575
594
 
576
595
  - [React Docs](https://react.dev) - Official documentation
577
596
  - [React DevTools](https://react.dev/learn/react-developer-tools) - Browser extension
578
597
  - [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks) - Hook constraints
579
598
  - [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app) - TS patterns
599
+ - [Vercel React Best Practices](https://github.com/vercel/react-best-practices) - Performance patterns
600
+ - `references/performance.md` - Detailed optimization guide
@@ -0,0 +1,573 @@
1
+ # React Performance Optimization
2
+
3
+ > Critical performance patterns from Vercel Engineering
4
+
5
+ ## Re-render Optimization
6
+
7
+ ### Functional setState Updates
8
+
9
+ **Impact**: Prevents stale closures, enables stable callbacks
10
+
11
+ Always use functional updates when state depends on current state:
12
+
13
+ ```typescript
14
+ // ❌ Incorrect: stale closure risk
15
+ const removeItem = useCallback((id: string) => {
16
+ setItems(items.filter(item => item.id !== id))
17
+ }, []) // Missing items dependency - STALE
18
+
19
+ // ✅ Correct: always uses latest state
20
+ const removeItem = useCallback((id: string) => {
21
+ setItems(curr => curr.filter(item => item.id !== id))
22
+ }, []) // Safe and stable
23
+ ```
24
+
25
+ ### Lazy State Initialization
26
+
27
+ **Impact**: Expensive initial computation runs only once
28
+
29
+ Pass function to `useState` for expensive initial values:
30
+
31
+ ```typescript
32
+ // ❌ Incorrect: runs on every render
33
+ const [settings, setSettings] = useState(
34
+ JSON.parse(localStorage.getItem('settings') || '{}')
35
+ )
36
+
37
+ // ✅ Correct: runs only once
38
+ const [settings, setSettings] = useState(() => {
39
+ const stored = localStorage.getItem('settings')
40
+ return stored ? JSON.parse(stored) : {}
41
+ })
42
+ ```
43
+
44
+ ### Use Transitions for Non-Urgent Updates
45
+
46
+ **Impact**: Maintains UI responsiveness
47
+
48
+ Mark frequent, non-urgent updates as transitions:
49
+
50
+ ```typescript
51
+ import { useTransition } from 'react'
52
+
53
+ function SearchResults() {
54
+ const [isPending, startTransition] = useTransition()
55
+ const [results, setResults] = useState([])
56
+
57
+ function handleSearch(query: string) {
58
+ startTransition(() => {
59
+ // Non-urgent: heavy filtering/search
60
+ setResults(expensiveSearch(query))
61
+ })
62
+ }
63
+
64
+ return (
65
+ <>
66
+ <input onChange={e => handleSearch(e.target.value)} />
67
+ {isPending && <Spinner />}
68
+ <Results data={results} />
69
+ </>
70
+ )
71
+ }
72
+ ```
73
+
74
+ ### Defer State Reads to Usage Point
75
+
76
+ **Impact**: Avoids unnecessary re-renders
77
+
78
+ Don't subscribe to state if only read in callbacks:
79
+
80
+ ```typescript
81
+ // ❌ Incorrect: component re-renders on every URL change
82
+ function Component() {
83
+ const searchParams = useSearchParams() // Re-renders on change
84
+
85
+ const handleSubmit = () => {
86
+ const query = searchParams.get('q') // Used only here
87
+ submitForm(query)
88
+ }
89
+
90
+ return <button onClick={handleSubmit}>Submit</button>
91
+ }
92
+
93
+ // ✅ Correct: no re-renders, read directly in callback
94
+ function Component() {
95
+ const handleSubmit = () => {
96
+ const query = new URLSearchParams(window.location.search).get('q')
97
+ submitForm(query)
98
+ }
99
+
100
+ return <button onClick={handleSubmit}>Submit</button>
101
+ }
102
+ ```
103
+
104
+ ### Extract to Memoized Components
105
+
106
+ **Impact**: Prevents unnecessary re-renders of expensive subtrees
107
+
108
+ ```typescript
109
+ // ❌ Incorrect: entire list re-renders on count change
110
+ function Page() {
111
+ const [count, setCount] = useState(0)
112
+ return (
113
+ <div>
114
+ <button onClick={() => setCount(c => c + 1)}>{count}</button>
115
+ <ExpensiveList items={items} /> {/* Re-renders unnecessarily */}
116
+ </div>
117
+ )
118
+ }
119
+
120
+ // ✅ Correct: list doesn't re-render
121
+ const MemoizedList = memo(ExpensiveList)
122
+
123
+ function Page() {
124
+ const [count, setCount] = useState(0)
125
+ return (
126
+ <div>
127
+ <button onClick={() => setCount(c => c + 1)}>{count}</button>
128
+ <MemoizedList items={items} />
129
+ </div>
130
+ )
131
+ }
132
+ ```
133
+
134
+ ### Narrow Effect Dependencies
135
+
136
+ **Impact**: Reduces effect re-runs
137
+
138
+ Extract only needed properties instead of whole objects:
139
+
140
+ ```typescript
141
+ // ❌ Incorrect: effect runs on any user change
142
+ useEffect(() => {
143
+ logUserActivity(user.id)
144
+ }, [user]) // user object changes often
145
+
146
+ // ✅ Correct: effect only runs when ID changes
147
+ useEffect(() => {
148
+ logUserActivity(user.id)
149
+ }, [user.id]) // Stable value
150
+ ```
151
+
152
+ ### Subscribe to Derived State
153
+
154
+ **Impact**: Avoids storing and synchronizing computed values
155
+
156
+ ```typescript
157
+ // ❌ Incorrect: derived state in useState (sync issue)
158
+ const [items, setItems] = useState([])
159
+ const [count, setCount] = useState(0)
160
+
161
+ useEffect(() => {
162
+ setCount(items.length) // Sync issue!
163
+ }, [items])
164
+
165
+ // ✅ Correct: compute during render
166
+ const [items, setItems] = useState([])
167
+ const count = items.length // Always in sync
168
+ ```
169
+
170
+ ## Rendering Performance
171
+
172
+ ### Hoist Static JSX Elements
173
+
174
+ **Impact**: Prevents recreation on every render
175
+
176
+ ```typescript
177
+ // ❌ Incorrect: creates new object on every render
178
+ function Component({ title }) {
179
+ return (
180
+ <div>
181
+ <Icon name="check" color="green" /> {/* New object every render */}
182
+ {title}
183
+ </div>
184
+ )
185
+ }
186
+
187
+ // ✅ Correct: created once
188
+ const CheckIcon = <Icon name="check" color="green" />
189
+
190
+ function Component({ title }) {
191
+ return (
192
+ <div>
193
+ {CheckIcon}
194
+ {title}
195
+ </div>
196
+ )
197
+ }
198
+ ```
199
+
200
+ ### CSS content-visibility for Long Lists
201
+
202
+ **Impact**: 10× faster initial render
203
+
204
+ Browser skips layout/paint for off-screen items:
205
+
206
+ ```css
207
+ .message-item {
208
+ content-visibility: auto;
209
+ contain-intrinsic-size: 0 80px; /* Estimated height */
210
+ }
211
+ ```
212
+
213
+ ### Use Explicit Conditional Rendering
214
+
215
+ **Impact**: Prevents rendering unwanted values
216
+
217
+ Use ternary when condition can be `0` or `NaN`:
218
+
219
+ ```typescript
220
+ // ❌ Incorrect: renders "0" when count is 0
221
+ {count && <div>{count} items</div>}
222
+
223
+ // ✅ Correct: renders nothing when 0
224
+ {count > 0 && <div>{count} items</div>}
225
+ // or
226
+ {count ? <div>{count} items</div> : null}
227
+ ```
228
+
229
+ ### Prevent Hydration Mismatch Without Flickering
230
+
231
+ **Impact**: No FOUC (Flash of Unstyled Content)
232
+
233
+ Use synchronous inline script:
234
+
235
+ ```tsx
236
+ function ThemeWrapper({ children }) {
237
+ return (
238
+ <>
239
+ <div id="theme-wrapper">{children}</div>
240
+ <script dangerouslySetInnerHTML={{__html: `
241
+ (function() {
242
+ try {
243
+ var theme = localStorage.getItem('theme') || 'light';
244
+ var el = document.getElementById('theme-wrapper');
245
+ if (el) el.className = theme;
246
+ } catch (e) {}
247
+ })();
248
+ `}} />
249
+ </>
250
+ )
251
+ }
252
+ ```
253
+
254
+ ### Animate SVG Wrapper Instead of Element
255
+
256
+ **Impact**: Reduces paint complexity
257
+
258
+ ```typescript
259
+ // ❌ Incorrect: animates SVG element directly
260
+ <svg style={{ transform: `translateX(${x}px)` }}>
261
+ <path d="..." />
262
+ </svg>
263
+
264
+ // ✅ Correct: animate wrapper div
265
+ <div style={{ transform: `translateX(${x}px)` }}>
266
+ <svg><path d="..." /></svg>
267
+ </div>
268
+ ```
269
+
270
+ ### Activity Component for Show/Hide
271
+
272
+ **Impact**: Prevents layout thrashing
273
+
274
+ ```typescript
275
+ // ❌ Incorrect: removes from DOM (layout thrash)
276
+ {isVisible && <ExpensiveComponent />}
277
+
278
+ // ✅ Correct: uses visibility (keeps layout)
279
+ <div style={{ visibility: isVisible ? 'visible' : 'hidden' }}>
280
+ <ExpensiveComponent />
281
+ </div>
282
+ ```
283
+
284
+ ## Client-Side Optimization
285
+
286
+ ### Use Passive Event Listeners
287
+
288
+ **Impact**: Eliminates scroll delay
289
+
290
+ Add `{ passive: true }` to touch/wheel events:
291
+
292
+ ```typescript
293
+ useEffect(() => {
294
+ const handleScroll = (e: Event) => {
295
+ // No preventDefault() call
296
+ updateScrollPosition()
297
+ }
298
+
299
+ // ✅ Passive listener - doesn't block scrolling
300
+ window.addEventListener('scroll', handleScroll, { passive: true })
301
+
302
+ return () => {
303
+ window.removeEventListener('scroll', handleScroll, { passive: true } as any)
304
+ }
305
+ }, [])
306
+ ```
307
+
308
+ ### Deduplicate Global Event Listeners
309
+
310
+ **Impact**: N instances = 1 listener instead of N
311
+
312
+ Use shared hook for global events:
313
+
314
+ ```typescript
315
+ // ❌ Incorrect: N listeners for N components
316
+ function Component() {
317
+ useEffect(() => {
318
+ const handler = () => console.log('resize')
319
+ window.addEventListener('resize', handler)
320
+ return () => window.removeEventListener('resize', handler)
321
+ }, [])
322
+ }
323
+
324
+ // ✅ Correct: 1 listener shared across all instances
325
+ import { useSWRSubscription } from 'swr/subscription'
326
+
327
+ function useWindowSize() {
328
+ return useSWRSubscription('window-size', (key, { next }) => {
329
+ const handler = () => next(null, {
330
+ width: window.innerWidth,
331
+ height: window.innerHeight
332
+ })
333
+
334
+ window.addEventListener('resize', handler)
335
+ handler() // Initial value
336
+
337
+ return () => window.removeEventListener('resize', handler)
338
+ })
339
+ }
340
+ ```
341
+
342
+ ### Cache Storage API Calls
343
+
344
+ **Impact**: Avoids synchronous I/O on every access
345
+
346
+ ```typescript
347
+ // ❌ Incorrect: reads from localStorage on every access
348
+ function useSettings() {
349
+ const [settings, setSettings] = useState(() => {
350
+ return JSON.parse(localStorage.getItem('settings') || '{}')
351
+ })
352
+
353
+ // Every time this runs, reads from localStorage
354
+ const value = localStorage.getItem('key')
355
+ }
356
+
357
+ // ✅ Correct: cache in memory
358
+ const settingsCache = new Map<string, any>()
359
+
360
+ function getCachedSetting(key: string) {
361
+ if (settingsCache.has(key)) {
362
+ return settingsCache.get(key)
363
+ }
364
+
365
+ const value = localStorage.getItem(key)
366
+ const parsed = value ? JSON.parse(value) : null
367
+ settingsCache.set(key, parsed)
368
+ return parsed
369
+ }
370
+
371
+ // Invalidate on storage events
372
+ window.addEventListener('storage', (e) => {
373
+ if (e.key) settingsCache.delete(e.key)
374
+ })
375
+ ```
376
+
377
+ ## JavaScript Performance
378
+
379
+ ### Build Index Maps for Repeated Lookups
380
+
381
+ **Impact**: O(n×m) → O(n+m), 1M ops → 2K ops
382
+
383
+ ```typescript
384
+ // ❌ Incorrect: O(n) per lookup
385
+ return orders.map(order => ({
386
+ ...order,
387
+ user: users.find(u => u.id === order.userId) // O(n) each time
388
+ }))
389
+
390
+ // ✅ Correct: O(1) per lookup
391
+ const userById = new Map(users.map(u => [u.id, u]))
392
+ return orders.map(order => ({
393
+ ...order,
394
+ user: userById.get(order.userId) // O(1)
395
+ }))
396
+ ```
397
+
398
+ ### Cache Property Access in Loops
399
+
400
+ **Impact**: Avoids repeated property lookups
401
+
402
+ ```typescript
403
+ // ❌ Incorrect: repeated access
404
+ for (let i = 0; i < items.length; i++) {
405
+ // items.length evaluated each iteration
406
+ }
407
+
408
+ // ✅ Correct: cached
409
+ const len = items.length
410
+ for (let i = 0; i < len; i++) {
411
+ // Faster
412
+ }
413
+ ```
414
+
415
+ ### Hoist RegExp Creation
416
+
417
+ **Impact**: Avoids creating regex on every call
418
+
419
+ ```typescript
420
+ // ❌ Incorrect: creates new RegExp every call
421
+ function validate(email: string) {
422
+ return /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(email)
423
+ }
424
+
425
+ // ✅ Correct: created once
426
+ const EMAIL_REGEX = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/
427
+
428
+ function validate(email: string) {
429
+ return EMAIL_REGEX.test(email)
430
+ }
431
+ ```
432
+
433
+ ### Use toSorted() Instead of sort()
434
+
435
+ **Impact**: Prevents mutation bugs in React state
436
+
437
+ ```typescript
438
+ // ❌ Incorrect: mutates array
439
+ setItems(items.sort((a, b) => a - b)) // MUTATION BUG
440
+
441
+ // ✅ Correct: creates new array (Chrome 110+)
442
+ setItems(items.toSorted((a, b) => a - b))
443
+
444
+ // ✅ Alternative: spread + sort
445
+ setItems([...items].sort((a, b) => a - b))
446
+ ```
447
+
448
+ ### Combine Multiple Array Iterations
449
+
450
+ **Impact**: O(3n) → O(n)
451
+
452
+ ```typescript
453
+ // ❌ Incorrect: 3 passes
454
+ const filtered = items.filter(x => x.active)
455
+ const mapped = filtered.map(x => x.value)
456
+ const result = mapped.slice(0, 10)
457
+
458
+ // ✅ Correct: 1 pass
459
+ const result = items
460
+ .filter(x => x.active)
461
+ .map(x => x.value)
462
+ .slice(0, 10)
463
+
464
+ // ✅ Even better: reduce for complex transforms
465
+ const result = items.reduce((acc, item) => {
466
+ if (item.active && acc.length < 10) {
467
+ acc.push(item.value)
468
+ }
469
+ return acc
470
+ }, [] as number[])
471
+ ```
472
+
473
+ ### Use Set/Map for O(1) Lookups
474
+
475
+ **Impact**: O(n) → O(1)
476
+
477
+ ```typescript
478
+ // ❌ Incorrect: O(n) lookup
479
+ const isAllowed = (id: string) => allowedIds.includes(id)
480
+
481
+ // ✅ Correct: O(1) lookup
482
+ const allowedSet = new Set(allowedIds)
483
+ const isAllowed = (id: string) => allowedSet.has(id)
484
+ ```
485
+
486
+ ### Early Length Check for Array Comparisons
487
+
488
+ **Impact**: Avoids unnecessary iteration
489
+
490
+ ```typescript
491
+ // ❌ Incorrect: iterates even when lengths differ
492
+ function arraysEqual(a: any[], b: any[]) {
493
+ return a.every((item, i) => item === b[i])
494
+ }
495
+
496
+ // ✅ Correct: check length first
497
+ function arraysEqual(a: any[], b: any[]) {
498
+ if (a.length !== b.length) return false
499
+ return a.every((item, i) => item === b[i])
500
+ }
501
+ ```
502
+
503
+ ## Advanced Patterns
504
+
505
+ ### Store Event Handlers in Refs
506
+
507
+ **Impact**: Stable references without useCallback
508
+
509
+ ```typescript
510
+ // ❌ Incorrect: needs dependencies
511
+ const handleClick = useCallback(() => {
512
+ doSomething(prop1, prop2)
513
+ }, [prop1, prop2]) // Changes often
514
+
515
+ // ✅ Correct: always stable
516
+ const handleClickRef = useRef<() => void>()
517
+ handleClickRef.current = () => {
518
+ doSomething(prop1, prop2)
519
+ }
520
+
521
+ const handleClick = useCallback(() => {
522
+ handleClickRef.current?.()
523
+ }, []) // Never changes
524
+ ```
525
+
526
+ ### useLatest for Stable Callback Refs
527
+
528
+ **Impact**: Avoids stale closures with stable identity
529
+
530
+ ```typescript
531
+ function useLatest<T>(value: T) {
532
+ const ref = useRef(value)
533
+ useEffect(() => {
534
+ ref.current = value
535
+ })
536
+ return ref
537
+ }
538
+
539
+ // Usage
540
+ function Component({ onEvent }) {
541
+ const onEventRef = useLatest(onEvent)
542
+
543
+ useEffect(() => {
544
+ const handler = () => onEventRef.current()
545
+ window.addEventListener('resize', handler)
546
+ return () => window.removeEventListener('resize', handler)
547
+ }, []) // Empty deps, always uses latest
548
+ }
549
+ ```
550
+
551
+ ## Anti-Patterns to Avoid
552
+
553
+ 1. ❌ Non-functional setState (stale closures)
554
+ 2. ❌ Missing lazy initialization for expensive initial state
555
+ 3. ❌ State reads in render when only used in callbacks
556
+ 4. ❌ Creating RegExp/objects in render
557
+ 5. ❌ Non-passive scroll/touch event listeners
558
+ 6. ❌ Multiple array iterations when one would suffice
559
+ 7. ❌ Using `&&` for conditional rendering with numbers
560
+ 8. ❌ Direct `.sort()` mutation in React state
561
+ 9. ❌ Storing derived values in state
562
+ 10. ❌ Not extracting expensive components to memo
563
+
564
+ ## React Compiler Note
565
+
566
+ Many of these optimizations become automatic with React Compiler:
567
+ - Auto-memoization of expensive computations
568
+ - Automatic extraction of stable dependencies
569
+ - Smart re-render optimization
570
+
571
+ However, architectural patterns (lazy state init, transitions, content-visibility) still require manual implementation.
572
+
573
+ **Reference**: [Vercel React Best Practices](https://github.com/vercel/react-best-practices)