@brookmind/ai-toolkit 1.1.7 → 1.2.1
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.
- package/LICENSE +21 -0
- package/README.md +42 -14
- package/dist/__tests__/constants.test.d.ts +2 -0
- package/dist/__tests__/constants.test.d.ts.map +1 -0
- package/dist/__tests__/constants.test.js +102 -0
- package/dist/__tests__/constants.test.js.map +1 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +114 -0
- package/dist/__tests__/index.test.js.map +1 -0
- package/dist/__tests__/integration/installer.test.d.ts +2 -0
- package/dist/__tests__/integration/installer.test.d.ts.map +1 -0
- package/dist/__tests__/integration/installer.test.js +425 -0
- package/dist/__tests__/integration/installer.test.js.map +1 -0
- package/dist/__tests__/services/installers.test.d.ts +2 -0
- package/dist/__tests__/services/installers.test.d.ts.map +1 -0
- package/dist/__tests__/services/installers.test.js +222 -0
- package/dist/__tests__/services/installers.test.js.map +1 -0
- package/dist/__tests__/services/opencode.test.d.ts +2 -0
- package/dist/__tests__/services/opencode.test.d.ts.map +1 -0
- package/dist/__tests__/services/opencode.test.js +120 -0
- package/dist/__tests__/services/opencode.test.js.map +1 -0
- package/dist/__tests__/ui/categorize.test.d.ts +2 -0
- package/dist/__tests__/ui/categorize.test.d.ts.map +1 -0
- package/dist/__tests__/ui/categorize.test.js +194 -0
- package/dist/__tests__/ui/categorize.test.js.map +1 -0
- package/dist/__tests__/ui/choices.test.d.ts +2 -0
- package/dist/__tests__/ui/choices.test.d.ts.map +1 -0
- package/dist/__tests__/ui/choices.test.js +180 -0
- package/dist/__tests__/ui/choices.test.js.map +1 -0
- package/dist/__tests__/ui/display.test.d.ts +2 -0
- package/dist/__tests__/ui/display.test.d.ts.map +1 -0
- package/dist/__tests__/ui/display.test.js +142 -0
- package/dist/__tests__/ui/display.test.js.map +1 -0
- package/dist/__tests__/utils/fs.test.d.ts +2 -0
- package/dist/__tests__/utils/fs.test.d.ts.map +1 -0
- package/dist/__tests__/utils/fs.test.js +142 -0
- package/dist/__tests__/utils/fs.test.js.map +1 -0
- package/dist/__tests__/utils/terminal.test.d.ts +2 -0
- package/dist/__tests__/utils/terminal.test.d.ts.map +1 -0
- package/dist/__tests__/utils/terminal.test.js +97 -0
- package/dist/__tests__/utils/terminal.test.js.map +1 -0
- package/dist/constants.d.ts +11 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +40 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -332
- package/dist/index.js.map +1 -1
- package/dist/services/installers.d.ts +8 -0
- package/dist/services/installers.d.ts.map +1 -0
- package/dist/services/installers.js +79 -0
- package/dist/services/installers.js.map +1 -0
- package/dist/services/opencode.d.ts +3 -0
- package/dist/services/opencode.d.ts.map +1 -0
- package/dist/services/opencode.js +33 -0
- package/dist/services/opencode.js.map +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/categorize.d.ts +6 -0
- package/dist/ui/categorize.d.ts.map +1 -0
- package/dist/ui/categorize.js +69 -0
- package/dist/ui/categorize.js.map +1 -0
- package/dist/ui/choices.d.ts +6 -0
- package/dist/ui/choices.d.ts.map +1 -0
- package/dist/ui/choices.js +70 -0
- package/dist/ui/choices.js.map +1 -0
- package/dist/ui/display.d.ts +8 -0
- package/dist/ui/display.d.ts.map +1 -0
- package/dist/ui/display.js +86 -0
- package/dist/ui/display.js.map +1 -0
- package/dist/utils/fs.d.ts +5 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +40 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/terminal.d.ts +5 -0
- package/dist/utils/terminal.d.ts.map +1 -0
- package/dist/utils/terminal.js +18 -0
- package/dist/utils/terminal.js.map +1 -0
- package/package.json +29 -5
- package/agents/code-reviewer.md +0 -35
- package/agents/code-simplifier.md +0 -52
- package/commands/create-pr-description.md +0 -102
- package/commands/create-pr.md +0 -76
- package/commands/create-react-tests.md +0 -207
- package/mcps/context7/.mcp.json +0 -13
- package/mcps/expo-mcp/.mcp.json +0 -13
- package/mcps/figma-mcp/.mcp.json +0 -10
- package/skills/github-cli/SKILL.md +0 -125
- package/skills/pdf-processing-pro/FORMS.md +0 -610
- package/skills/pdf-processing-pro/OCR.md +0 -137
- package/skills/pdf-processing-pro/SKILL.md +0 -296
- package/skills/pdf-processing-pro/TABLES.md +0 -626
- package/skills/pdf-processing-pro/scripts/analyze_form.py +0 -307
- package/skills/react-best-practices/AGENTS.md +0 -915
- package/skills/react-best-practices/README.md +0 -127
- package/skills/react-best-practices/SKILL.md +0 -110
- package/skills/react-best-practices/metadata.json +0 -14
- package/skills/react-best-practices/rules/_sections.md +0 -41
- package/skills/react-best-practices/rules/_template.md +0 -28
- package/skills/react-best-practices/rules/advanced-event-handler-refs.md +0 -80
- package/skills/react-best-practices/rules/advanced-use-latest.md +0 -76
- package/skills/react-best-practices/rules/async-defer-await.md +0 -80
- package/skills/react-best-practices/rules/async-dependencies.md +0 -36
- package/skills/react-best-practices/rules/async-parallel.md +0 -28
- package/skills/react-best-practices/rules/async-suspense-boundaries.md +0 -100
- package/skills/react-best-practices/rules/bundle-barrel-imports.md +0 -42
- package/skills/react-best-practices/rules/bundle-conditional.md +0 -106
- package/skills/react-best-practices/rules/bundle-preload.md +0 -44
- package/skills/react-best-practices/rules/client-event-listeners.md +0 -131
- package/skills/react-best-practices/rules/client-swr-dedup.md +0 -133
- package/skills/react-best-practices/rules/js-batch-dom-css.md +0 -82
- package/skills/react-best-practices/rules/js-cache-function-results.md +0 -80
- package/skills/react-best-practices/rules/js-cache-property-access.md +0 -28
- package/skills/react-best-practices/rules/js-cache-storage.md +0 -70
- package/skills/react-best-practices/rules/js-combine-iterations.md +0 -32
- package/skills/react-best-practices/rules/js-early-exit.md +0 -50
- package/skills/react-best-practices/rules/js-hoist-regexp.md +0 -45
- package/skills/react-best-practices/rules/js-index-maps.md +0 -37
- package/skills/react-best-practices/rules/js-length-check-first.md +0 -49
- package/skills/react-best-practices/rules/js-min-max-loop.md +0 -82
- package/skills/react-best-practices/rules/js-set-map-lookups.md +0 -24
- package/skills/react-best-practices/rules/js-tosorted-immutable.md +0 -57
- package/skills/react-best-practices/rules/rendering-activity.md +0 -90
- package/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
- package/skills/react-best-practices/rules/rendering-conditional-render.md +0 -40
- package/skills/react-best-practices/rules/rendering-content-visibility.md +0 -38
- package/skills/react-best-practices/rules/rendering-hoist-jsx.md +0 -65
- package/skills/react-best-practices/rules/rendering-svg-precision.md +0 -28
- package/skills/react-best-practices/rules/rerender-defer-reads.md +0 -39
- package/skills/react-best-practices/rules/rerender-dependencies.md +0 -45
- package/skills/react-best-practices/rules/rerender-derived-state.md +0 -29
- package/skills/react-best-practices/rules/rerender-functional-setstate.md +0 -74
- package/skills/react-best-practices/rules/rerender-lazy-state-init.md +0 -58
- package/skills/react-best-practices/rules/rerender-memo.md +0 -85
- package/skills/react-best-practices/rules/rerender-transitions.md +0 -40
- package/skills/skill-creator/LICENSE.txt +0 -202
- package/skills/skill-creator/SKILL.md +0 -209
- package/skills/skill-creator/scripts/init_skill.py +0 -303
- package/skills/skill-creator/scripts/package_skill.py +0 -110
- package/skills/skill-creator/scripts/quick_validate.py +0 -65
- package/skills/spring-boot-development/EXAMPLES.md +0 -2346
- package/skills/spring-boot-development/README.md +0 -595
- package/skills/spring-boot-development/SKILL.md +0 -1519
- package/themes/README.md +0 -68
- package/themes/claude-vivid.json +0 -72
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Use Explicit Conditional Rendering
|
|
3
|
-
impact: LOW
|
|
4
|
-
impactDescription: prevents rendering 0 or NaN
|
|
5
|
-
tags: rendering, conditional, jsx, falsy-values
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Use Explicit Conditional Rendering
|
|
9
|
-
|
|
10
|
-
Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render.
|
|
11
|
-
|
|
12
|
-
**Incorrect (renders "0" when count is 0):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
function Badge({ count }: { count: number }) {
|
|
16
|
-
return (
|
|
17
|
-
<div>
|
|
18
|
-
{count && <span className="badge">{count}</span>}
|
|
19
|
-
</div>
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// When count = 0, renders: <div>0</div>
|
|
24
|
-
// When count = 5, renders: <div><span class="badge">5</span></div>
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**Correct (renders nothing when count is 0):**
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
function Badge({ count }: { count: number }) {
|
|
31
|
-
return (
|
|
32
|
-
<div>
|
|
33
|
-
{count > 0 ? <span className="badge">{count}</span> : null}
|
|
34
|
-
</div>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// When count = 0, renders: <div></div>
|
|
39
|
-
// When count = 5, renders: <div><span class="badge">5</span></div>
|
|
40
|
-
```
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: CSS content-visibility for Long Lists
|
|
3
|
-
impact: HIGH
|
|
4
|
-
impactDescription: faster initial render
|
|
5
|
-
tags: rendering, css, content-visibility, long-lists
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## CSS content-visibility for Long Lists
|
|
9
|
-
|
|
10
|
-
Apply `content-visibility: auto` to defer off-screen rendering.
|
|
11
|
-
|
|
12
|
-
**CSS:**
|
|
13
|
-
|
|
14
|
-
```css
|
|
15
|
-
.message-item {
|
|
16
|
-
content-visibility: auto;
|
|
17
|
-
contain-intrinsic-size: 0 80px;
|
|
18
|
-
}
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
**Example:**
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
function MessageList({ messages }: { messages: Message[] }) {
|
|
25
|
-
return (
|
|
26
|
-
<div className="overflow-y-auto h-screen">
|
|
27
|
-
{messages.map(msg => (
|
|
28
|
-
<div key={msg.id} className="message-item">
|
|
29
|
-
<Avatar user={msg.author} />
|
|
30
|
-
<div>{msg.content}</div>
|
|
31
|
-
</div>
|
|
32
|
-
))}
|
|
33
|
-
</div>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render).
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Hoist Static JSX Elements
|
|
3
|
-
impact: LOW
|
|
4
|
-
impactDescription: avoids re-creation
|
|
5
|
-
tags: rendering, jsx, static, react-compiler, optimization
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Hoist Static JSX Elements
|
|
9
|
-
|
|
10
|
-
With React 19 Compiler, static JSX is automatically hoisted. Without the Compiler, manually extract static JSX outside components to avoid re-creation.
|
|
11
|
-
|
|
12
|
-
**With React 19 Compiler (Recommended):**
|
|
13
|
-
|
|
14
|
-
The React Compiler automatically detects and hoists static JSX. Just write normal components:
|
|
15
|
-
|
|
16
|
-
```tsx
|
|
17
|
-
function LoadingSkeleton() {
|
|
18
|
-
return <div className="animate-pulse h-20 bg-gray-200" />;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function Container({ loading }: { loading: boolean }) {
|
|
22
|
-
return <div>{loading && <LoadingSkeleton />}</div>;
|
|
23
|
-
}
|
|
24
|
-
// Compiler automatically optimizes this!
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**Without React Compiler (Manual Optimization):**
|
|
28
|
-
|
|
29
|
-
If you're not using the Compiler, manually hoist static elements:
|
|
30
|
-
|
|
31
|
-
**Before (recreates element every render):**
|
|
32
|
-
|
|
33
|
-
```tsx
|
|
34
|
-
function Container({ loading }: { loading: boolean }) {
|
|
35
|
-
// This JSX is recreated on every render
|
|
36
|
-
const skeleton = <div className="animate-pulse h-20 bg-gray-200" />;
|
|
37
|
-
|
|
38
|
-
return <div>{loading && skeleton}</div>;
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**After (reuses same element):**
|
|
43
|
-
|
|
44
|
-
```tsx
|
|
45
|
-
// Hoisted to module scope - created once
|
|
46
|
-
const loadingSkeleton = <div className="animate-pulse h-20 bg-gray-200" />;
|
|
47
|
-
|
|
48
|
-
function Container({ loading }: { loading: boolean }) {
|
|
49
|
-
return <div>{loading && loadingSkeleton}</div>;
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**Best use cases for manual hoisting:**
|
|
54
|
-
|
|
55
|
-
- Large static SVG icons (expensive to recreate)
|
|
56
|
-
- Complex static layouts that never change
|
|
57
|
-
- Decorative elements with no dynamic props
|
|
58
|
-
|
|
59
|
-
**When NOT to hoist:**
|
|
60
|
-
|
|
61
|
-
- Elements with dynamic props or children
|
|
62
|
-
- Elements that need access to component scope (hooks, state)
|
|
63
|
-
- Small simple elements (overhead not worth it)
|
|
64
|
-
|
|
65
|
-
Reference: [React Compiler](https://react.dev/learn/react-compiler)
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Optimize SVG Precision
|
|
3
|
-
impact: LOW
|
|
4
|
-
impactDescription: reduces file size
|
|
5
|
-
tags: rendering, svg, optimization, svgo
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Optimize SVG Precision
|
|
9
|
-
|
|
10
|
-
Reduce SVG coordinate precision to decrease file size. The optimal precision depends on the viewBox size, but in general reducing precision should be considered.
|
|
11
|
-
|
|
12
|
-
**Incorrect (excessive precision):**
|
|
13
|
-
|
|
14
|
-
```svg
|
|
15
|
-
<path d="M 10.293847 20.847362 L 30.938472 40.192837" />
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**Correct (1 decimal place):**
|
|
19
|
-
|
|
20
|
-
```svg
|
|
21
|
-
<path d="M 10.3 20.8 L 30.9 40.2" />
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
**Automate with SVGO:**
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npx svgo --precision=1 --multipass icon.svg
|
|
28
|
-
```
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Defer State Reads to Usage Point
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: avoids unnecessary subscriptions
|
|
5
|
-
tags: rerender, searchParams, localStorage, optimization
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Defer State Reads to Usage Point
|
|
9
|
-
|
|
10
|
-
Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks.
|
|
11
|
-
|
|
12
|
-
**Incorrect (subscribes to all searchParams changes):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
function ShareButton({ chatId }: { chatId: string }) {
|
|
16
|
-
const searchParams = useSearchParams()
|
|
17
|
-
|
|
18
|
-
const handleShare = () => {
|
|
19
|
-
const ref = searchParams.get('ref')
|
|
20
|
-
shareChat(chatId, { ref })
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return <button onClick={handleShare}>Share</button>
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
**Correct (reads on demand, no subscription):**
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
function ShareButton({ chatId }: { chatId: string }) {
|
|
31
|
-
const handleShare = () => {
|
|
32
|
-
const params = new URLSearchParams(window.location.search)
|
|
33
|
-
const ref = params.get('ref')
|
|
34
|
-
shareChat(chatId, { ref })
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return <button onClick={handleShare}>Share</button>
|
|
38
|
-
}
|
|
39
|
-
```
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Narrow Effect Dependencies
|
|
3
|
-
impact: LOW
|
|
4
|
-
impactDescription: minimizes effect re-runs
|
|
5
|
-
tags: rerender, useEffect, dependencies, optimization
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Narrow Effect Dependencies
|
|
9
|
-
|
|
10
|
-
Specify primitive dependencies instead of objects to minimize effect re-runs.
|
|
11
|
-
|
|
12
|
-
**Incorrect (re-runs on any user field change):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
console.log(user.id)
|
|
17
|
-
}, [user])
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
**Correct (re-runs only when id changes):**
|
|
21
|
-
|
|
22
|
-
```tsx
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
console.log(user.id)
|
|
25
|
-
}, [user.id])
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
**For derived state, compute outside effect:**
|
|
29
|
-
|
|
30
|
-
```tsx
|
|
31
|
-
// Incorrect: runs on width=767, 766, 765...
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (width < 768) {
|
|
34
|
-
enableMobileMode()
|
|
35
|
-
}
|
|
36
|
-
}, [width])
|
|
37
|
-
|
|
38
|
-
// Correct: runs only on boolean transition
|
|
39
|
-
const isMobile = width < 768
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
if (isMobile) {
|
|
42
|
-
enableMobileMode()
|
|
43
|
-
}
|
|
44
|
-
}, [isMobile])
|
|
45
|
-
```
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Subscribe to Derived State
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: reduces re-render frequency
|
|
5
|
-
tags: rerender, derived-state, media-query, optimization
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Subscribe to Derived State
|
|
9
|
-
|
|
10
|
-
Subscribe to derived boolean state instead of continuous values to reduce re-render frequency.
|
|
11
|
-
|
|
12
|
-
**Incorrect (re-renders on every pixel change):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
function Sidebar() {
|
|
16
|
-
const width = useWindowWidth() // updates continuously
|
|
17
|
-
const isMobile = width < 768
|
|
18
|
-
return <nav className={isMobile ? 'mobile' : 'desktop'}>
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**Correct (re-renders only when boolean changes):**
|
|
23
|
-
|
|
24
|
-
```tsx
|
|
25
|
-
function Sidebar() {
|
|
26
|
-
const isMobile = useMediaQuery('(max-width: 767px)')
|
|
27
|
-
return <nav className={isMobile ? 'mobile' : 'desktop'}>
|
|
28
|
-
}
|
|
29
|
-
```
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Use Functional setState Updates
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: prevents stale closures and unnecessary callback recreations
|
|
5
|
-
tags: react, hooks, useState, useCallback, callbacks, closures
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Use Functional setState Updates
|
|
9
|
-
|
|
10
|
-
When updating state based on the current state value, use the functional update form of setState instead of directly referencing the state variable. This prevents stale closures, eliminates unnecessary dependencies, and creates stable callback references.
|
|
11
|
-
|
|
12
|
-
**Incorrect (requires state as dependency):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
function TodoList() {
|
|
16
|
-
const [items, setItems] = useState(initialItems)
|
|
17
|
-
|
|
18
|
-
// Callback must depend on items, recreated on every items change
|
|
19
|
-
const addItems = useCallback((newItems: Item[]) => {
|
|
20
|
-
setItems([...items, ...newItems])
|
|
21
|
-
}, [items]) // ❌ items dependency causes recreations
|
|
22
|
-
|
|
23
|
-
// Risk of stale closure if dependency is forgotten
|
|
24
|
-
const removeItem = useCallback((id: string) => {
|
|
25
|
-
setItems(items.filter(item => item.id !== id))
|
|
26
|
-
}, []) // ❌ Missing items dependency - will use stale items!
|
|
27
|
-
|
|
28
|
-
return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
The first callback is recreated every time `items` changes, which can cause child components to re-render unnecessarily. The second callback has a stale closure bug—it will always reference the initial `items` value.
|
|
33
|
-
|
|
34
|
-
**Correct (stable callbacks, no stale closures):**
|
|
35
|
-
|
|
36
|
-
```tsx
|
|
37
|
-
function TodoList() {
|
|
38
|
-
const [items, setItems] = useState(initialItems)
|
|
39
|
-
|
|
40
|
-
// Stable callback, never recreated
|
|
41
|
-
const addItems = useCallback((newItems: Item[]) => {
|
|
42
|
-
setItems(curr => [...curr, ...newItems])
|
|
43
|
-
}, []) // ✅ No dependencies needed
|
|
44
|
-
|
|
45
|
-
// Always uses latest state, no stale closure risk
|
|
46
|
-
const removeItem = useCallback((id: string) => {
|
|
47
|
-
setItems(curr => curr.filter(item => item.id !== id))
|
|
48
|
-
}, []) // ✅ Safe and stable
|
|
49
|
-
|
|
50
|
-
return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
**Benefits:**
|
|
55
|
-
|
|
56
|
-
1. **Stable callback references** - Callbacks don't need to be recreated when state changes
|
|
57
|
-
2. **No stale closures** - Always operates on the latest state value
|
|
58
|
-
3. **Fewer dependencies** - Simplifies dependency arrays and reduces memory leaks
|
|
59
|
-
4. **Prevents bugs** - Eliminates the most common source of React closure bugs
|
|
60
|
-
|
|
61
|
-
**When to use functional updates:**
|
|
62
|
-
|
|
63
|
-
- Any setState that depends on the current state value
|
|
64
|
-
- Inside useCallback/useMemo when state is needed
|
|
65
|
-
- Event handlers that reference state
|
|
66
|
-
- Async operations that update state
|
|
67
|
-
|
|
68
|
-
**When direct updates are fine:**
|
|
69
|
-
|
|
70
|
-
- Setting state to a static value: `setCount(0)`
|
|
71
|
-
- Setting state from props/arguments only: `setName(newName)`
|
|
72
|
-
- State doesn't depend on previous value
|
|
73
|
-
|
|
74
|
-
**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Use Lazy State Initialization
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: wasted computation on every render
|
|
5
|
-
tags: react, hooks, useState, performance, initialization
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Use Lazy State Initialization
|
|
9
|
-
|
|
10
|
-
Pass a function to `useState` for expensive initial values. Without the function form, the initializer runs on every render even though the value is only used once.
|
|
11
|
-
|
|
12
|
-
**Incorrect (runs on every render):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
function FilteredList({ items }: { items: Item[] }) {
|
|
16
|
-
// buildSearchIndex() runs on EVERY render, even after initialization
|
|
17
|
-
const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
|
|
18
|
-
const [query, setQuery] = useState('')
|
|
19
|
-
|
|
20
|
-
// When query changes, buildSearchIndex runs again unnecessarily
|
|
21
|
-
return <SearchResults index={searchIndex} query={query} />
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function UserProfile() {
|
|
25
|
-
// JSON.parse runs on every render
|
|
26
|
-
const [settings, setSettings] = useState(
|
|
27
|
-
JSON.parse(localStorage.getItem('settings') || '{}')
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
return <SettingsForm settings={settings} onChange={setSettings} />
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
**Correct (runs only once):**
|
|
35
|
-
|
|
36
|
-
```tsx
|
|
37
|
-
function FilteredList({ items }: { items: Item[] }) {
|
|
38
|
-
// buildSearchIndex() runs ONLY on initial render
|
|
39
|
-
const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
|
|
40
|
-
const [query, setQuery] = useState('')
|
|
41
|
-
|
|
42
|
-
return <SearchResults index={searchIndex} query={query} />
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function UserProfile() {
|
|
46
|
-
// JSON.parse runs only on initial render
|
|
47
|
-
const [settings, setSettings] = useState(() => {
|
|
48
|
-
const stored = localStorage.getItem('settings')
|
|
49
|
-
return stored ? JSON.parse(stored) : {}
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
return <SettingsForm settings={settings} onChange={setSettings} />
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Use lazy initialization when computing initial values from localStorage/sessionStorage, building data structures (indexes, maps), reading from the DOM, or performing heavy transformations.
|
|
57
|
-
|
|
58
|
-
For simple primitives (`useState(0)`), direct references (`useState(props.value)`), or cheap literals (`useState({})`), the function form is unnecessary.
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Extract to Memoized Components
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: enables early returns and skips computation
|
|
5
|
-
tags: rerender, memo, useMemo, react-compiler, optimization
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Extract to Memoized Components
|
|
9
|
-
|
|
10
|
-
Extract expensive work into separate components to enable early returns before computation. With React 19 Compiler, this happens automatically.
|
|
11
|
-
|
|
12
|
-
**With React 19 Compiler (Recommended):**
|
|
13
|
-
|
|
14
|
-
If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, you don't need manual memoization. The compiler automatically:
|
|
15
|
-
|
|
16
|
-
- Memoizes component renders
|
|
17
|
-
- Caches expensive computations
|
|
18
|
-
- Optimizes re-renders
|
|
19
|
-
|
|
20
|
-
Just write clean code without `memo()` or `useMemo()`:
|
|
21
|
-
|
|
22
|
-
```tsx
|
|
23
|
-
function UserAvatar({ user }: { user: User }) {
|
|
24
|
-
const id = computeAvatarId(user); // Compiler auto-memoizes
|
|
25
|
-
return <Avatar id={id} />;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function Profile({ user, loading }: Props) {
|
|
29
|
-
if (loading) return <Skeleton />;
|
|
30
|
-
return (
|
|
31
|
-
<div>
|
|
32
|
-
<UserAvatar user={user} /> {/* Compiler auto-memoizes */}
|
|
33
|
-
</div>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Without React Compiler (Manual Optimization):**
|
|
39
|
-
|
|
40
|
-
If you're not using the React Compiler, use `memo()` and `useMemo()` manually:
|
|
41
|
-
|
|
42
|
-
**Incorrect (computes avatar even when loading):**
|
|
43
|
-
|
|
44
|
-
```tsx
|
|
45
|
-
function Profile({ user, loading }: Props) {
|
|
46
|
-
// This runs on every render, even when loading=true
|
|
47
|
-
const avatar = useMemo(() => {
|
|
48
|
-
const id = computeAvatarId(user);
|
|
49
|
-
return <Avatar id={id} />;
|
|
50
|
-
}, [user]);
|
|
51
|
-
|
|
52
|
-
if (loading) return <Skeleton />;
|
|
53
|
-
return <div>{avatar}</div>;
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**Correct (skips computation when loading):**
|
|
58
|
-
|
|
59
|
-
```tsx
|
|
60
|
-
const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
|
|
61
|
-
const id = useMemo(() => computeAvatarId(user), [user]);
|
|
62
|
-
return <Avatar id={id} />;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
function Profile({ user, loading }: Props) {
|
|
66
|
-
if (loading) return <Skeleton />;
|
|
67
|
-
return (
|
|
68
|
-
<div>
|
|
69
|
-
<UserAvatar user={user} />
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**Key insight:** Early returns before component JSX skip that component's computation entirely. Extract expensive work into child components that appear after conditionals.
|
|
76
|
-
|
|
77
|
-
**When to still use memo() with Compiler:**
|
|
78
|
-
|
|
79
|
-
Even with the Compiler, `memo()` can be useful for:
|
|
80
|
-
|
|
81
|
-
- Components receiving frequently-changing callback props
|
|
82
|
-
- Components in lists with stable item identity
|
|
83
|
-
- Performance-critical components where you want explicit control
|
|
84
|
-
|
|
85
|
-
Reference: [React Compiler](https://react.dev/learn/react-compiler)
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Use Transitions for Non-Urgent Updates
|
|
3
|
-
impact: MEDIUM
|
|
4
|
-
impactDescription: maintains UI responsiveness
|
|
5
|
-
tags: rerender, transitions, startTransition, performance
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## Use Transitions for Non-Urgent Updates
|
|
9
|
-
|
|
10
|
-
Mark frequent, non-urgent state updates as transitions to maintain UI responsiveness.
|
|
11
|
-
|
|
12
|
-
**Incorrect (blocks UI on every scroll):**
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
function ScrollTracker() {
|
|
16
|
-
const [scrollY, setScrollY] = useState(0)
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
const handler = () => setScrollY(window.scrollY)
|
|
19
|
-
window.addEventListener('scroll', handler, { passive: true })
|
|
20
|
-
return () => window.removeEventListener('scroll', handler)
|
|
21
|
-
}, [])
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Correct (non-blocking updates):**
|
|
26
|
-
|
|
27
|
-
```tsx
|
|
28
|
-
import { startTransition } from 'react'
|
|
29
|
-
|
|
30
|
-
function ScrollTracker() {
|
|
31
|
-
const [scrollY, setScrollY] = useState(0)
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
const handler = () => {
|
|
34
|
-
startTransition(() => setScrollY(window.scrollY))
|
|
35
|
-
}
|
|
36
|
-
window.addEventListener('scroll', handler, { passive: true })
|
|
37
|
-
return () => window.removeEventListener('scroll', handler)
|
|
38
|
-
}, [])
|
|
39
|
-
}
|
|
40
|
-
```
|