@mind-fold/open-flow 0.2.10 → 0.2.12
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/dist/templates/commands/break-loop.txt +1 -1
- package/dist/templates/commands/check-cross-layer.txt +3 -3
- package/dist/templates/commands/create-command.txt +5 -5
- package/dist/templates/commands/extract-llm-docs.txt +18 -18
- package/dist/templates/commands/extract-to-rules.txt +38 -38
- package/dist/templates/commands/finish-work.txt +5 -5
- package/dist/templates/commands/generate-backend-structure.txt +27 -27
- package/dist/templates/commands/generate-frontend-structure.txt +15 -15
- package/dist/templates/commands/integrate-skill.txt +30 -30
- package/dist/templates/commands/onboard-developer.txt +487 -39
- package/dist/templates/commands/record-agent-flow.txt +4 -4
- package/dist/templates/commands/sync-from-runtime.txt +2 -2
- package/dist/templates/markdown/agent-progress-index.md.txt +9 -9
- package/dist/templates/markdown/backend-doc.md.txt +12 -12
- package/dist/templates/markdown/flow.md.txt +99 -99
- package/dist/templates/markdown/frontend-doc.md.txt +11 -11
- package/dist/templates/markdown/structure/backend/database-guidelines.md.txt +6 -6
- package/dist/templates/markdown/structure/backend/directory-structure.md.txt +28 -28
- package/dist/templates/markdown/structure/backend/index.md.txt +10 -10
- package/dist/templates/markdown/structure/backend/logging-guidelines.md.txt +4 -4
- package/dist/templates/markdown/structure/backend/quality-guidelines.md.txt +12 -12
- package/dist/templates/markdown/structure/backend/type-safety.md.txt +6 -6
- package/dist/templates/markdown/structure/flows/code-reuse-thinking-guide.md.txt +17 -17
- package/dist/templates/markdown/structure/flows/cross-layer-thinking-guide.md.txt +96 -96
- package/dist/templates/markdown/structure/flows/index.md.txt +31 -31
- package/dist/templates/markdown/structure/flows/pre-implementation-checklist.md.txt +19 -19
- package/dist/templates/markdown/structure/flows/spec-flow-template.md.txt +20 -20
- package/dist/templates/markdown/structure/frontend/component-guidelines.md.txt +12 -12
- package/dist/templates/markdown/structure/frontend/directory-structure.md.txt +61 -61
- package/dist/templates/markdown/structure/frontend/hook-guidelines.md.txt +5 -5
- package/dist/templates/markdown/structure/frontend/index.md.txt +13 -13
- package/dist/templates/markdown/structure/frontend/quality-guidelines.md.txt +21 -21
- package/dist/templates/markdown/structure/frontend/state-management.md.txt +12 -12
- package/dist/templates/markdown/structure/frontend/type-safety.md.txt +11 -11
- package/dist/templates/scripts/add-session.sh.txt +5 -5
- package/dist/templates/scripts/extract-md-headings.sh.txt +3 -3
- package/dist/templates/scripts/get-context.sh.txt +1 -1
- package/dist/templates/scripts/init-developer.sh.txt +1 -1
- package/dist/templates/scripts/update-index.sh.txt +2 -2
- package/package.json +1 -1
|
@@ -16,23 +16,23 @@
|
|
|
16
16
|
|
|
17
17
|
```
|
|
18
18
|
[Source]
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
[Layer 1]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
[Layer 2]
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
[Layer 3]
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
|
|
|
20
|
+
v
|
|
21
|
+
[Layer 1] -----------------------------+
|
|
22
|
+
| |
|
|
23
|
+
| Data: { field1, field2 } |
|
|
24
|
+
| Transform: none |
|
|
25
|
+
| |
|
|
26
|
+
v |
|
|
27
|
+
[Layer 2] -----------------------------|
|
|
28
|
+
| |
|
|
29
|
+
| Data: { field1, field2, extra } |
|
|
30
|
+
| Transform: add computed field |
|
|
31
|
+
| |
|
|
32
|
+
v |
|
|
33
|
+
[Layer 3] -----------------------------+
|
|
34
|
+
|
|
|
35
|
+
v
|
|
36
36
|
[Consumer]
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
**Transformation**: None (pass-through)
|
|
64
64
|
|
|
65
65
|
**Error Handling**:
|
|
66
|
-
- Validation errors
|
|
67
|
-
- Not found
|
|
66
|
+
- Validation errors -> 400 response
|
|
67
|
+
- Not found -> 404 response
|
|
68
68
|
|
|
69
69
|
---
|
|
70
70
|
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
- Adds `computed` field based on `field1` and `field2`
|
|
88
88
|
|
|
89
89
|
**Error Handling**:
|
|
90
|
-
- Business logic errors
|
|
90
|
+
- Business logic errors -> throw CustomError
|
|
91
91
|
|
|
92
92
|
---
|
|
93
93
|
|
|
@@ -68,10 +68,10 @@ Use proper HTML elements for their intended purpose.
|
|
|
68
68
|
### Interactive Elements
|
|
69
69
|
|
|
70
70
|
```typescript
|
|
71
|
-
//
|
|
71
|
+
// [OK] GOOD: Use button for actions
|
|
72
72
|
<button onClick={handleClick}>Click me</button>
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// [X] BAD: Don't use div as button
|
|
75
75
|
<div role="button" onClick={handleClick}>Click me</div>
|
|
76
76
|
```
|
|
77
77
|
|
|
@@ -84,14 +84,14 @@ Use proper HTML elements for their intended purpose.
|
|
|
84
84
|
// Link: navigates to a URL
|
|
85
85
|
<Link href="/about">About</Link>
|
|
86
86
|
|
|
87
|
-
//
|
|
87
|
+
// [X] BAD: Button that looks like a link but navigates
|
|
88
88
|
<button onClick={() => router.push('/about')}>About</button>
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
### Headings
|
|
92
92
|
|
|
93
93
|
```typescript
|
|
94
|
-
//
|
|
94
|
+
// [OK] GOOD: Proper heading hierarchy
|
|
95
95
|
<article>
|
|
96
96
|
<h1>Page Title</h1>
|
|
97
97
|
<section>
|
|
@@ -100,7 +100,7 @@ Use proper HTML elements for their intended purpose.
|
|
|
100
100
|
</section>
|
|
101
101
|
</article>
|
|
102
102
|
|
|
103
|
-
//
|
|
103
|
+
// [X] BAD: Skipping heading levels
|
|
104
104
|
<h1>Title</h1>
|
|
105
105
|
<h3>Subsection</h3> // Missing h2
|
|
106
106
|
```
|
|
@@ -108,14 +108,14 @@ Use proper HTML elements for their intended purpose.
|
|
|
108
108
|
### Lists
|
|
109
109
|
|
|
110
110
|
```typescript
|
|
111
|
-
//
|
|
111
|
+
// [OK] GOOD: Use list elements for lists
|
|
112
112
|
<ul>
|
|
113
113
|
{items.map(item => (
|
|
114
114
|
<li key={item.id}>{item.name}</li>
|
|
115
115
|
))}
|
|
116
116
|
</ul>
|
|
117
117
|
|
|
118
|
-
//
|
|
118
|
+
// [X] BAD: Divs for list
|
|
119
119
|
<div>
|
|
120
120
|
{items.map(item => (
|
|
121
121
|
<div key={item.id}>{item.name}</div>
|
|
@@ -132,7 +132,7 @@ Use proper HTML elements for their intended purpose.
|
|
|
132
132
|
```typescript
|
|
133
133
|
import Image from 'next/image';
|
|
134
134
|
|
|
135
|
-
//
|
|
135
|
+
// [OK] GOOD: Use Next.js Image
|
|
136
136
|
<Image
|
|
137
137
|
src="/avatar.png"
|
|
138
138
|
alt="User avatar"
|
|
@@ -140,7 +140,7 @@ import Image from 'next/image';
|
|
|
140
140
|
height={100}
|
|
141
141
|
/>
|
|
142
142
|
|
|
143
|
-
//
|
|
143
|
+
// [X] BAD: Native img tag
|
|
144
144
|
<img src="/avatar.png" alt="User avatar" />
|
|
145
145
|
```
|
|
146
146
|
|
|
@@ -172,17 +172,17 @@ module.exports = {
|
|
|
172
172
|
### Labels for Inputs
|
|
173
173
|
|
|
174
174
|
```typescript
|
|
175
|
-
//
|
|
175
|
+
// [OK] GOOD: Associated label
|
|
176
176
|
<label htmlFor="email">Email</label>
|
|
177
177
|
<input id="email" type="email" />
|
|
178
178
|
|
|
179
|
-
//
|
|
179
|
+
// [OK] GOOD: Implicit label
|
|
180
180
|
<label>
|
|
181
181
|
Email
|
|
182
182
|
<input type="email" />
|
|
183
183
|
</label>
|
|
184
184
|
|
|
185
|
-
//
|
|
185
|
+
// [X] BAD: No label
|
|
186
186
|
<input type="email" placeholder="Email" />
|
|
187
187
|
```
|
|
188
188
|
|
|
@@ -8,37 +8,37 @@
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
src/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
11
|
+
|-- app/ # Next.js App Router (if using Next.js)
|
|
12
|
+
| |-- (marketing)/ # Marketing pages group
|
|
13
|
+
| |-- (app)/ # Application pages group
|
|
14
|
+
| \-- api/ # API routes
|
|
15
|
+
|
|
|
16
|
+
|-- components/ # Shared components
|
|
17
|
+
| |-- ui/ # Base UI components (Button, Input, etc.)
|
|
18
|
+
| \-- common/ # Composite components (Header, Footer, etc.)
|
|
19
|
+
|
|
|
20
|
+
|-- modules/ # Feature modules
|
|
21
|
+
| \-- {feature}/
|
|
22
|
+
| |-- components/ # Feature-specific components
|
|
23
|
+
| |-- hooks/ # Feature-specific hooks
|
|
24
|
+
| |-- context/ # Feature-specific context
|
|
25
|
+
| |-- lib/ # Feature-specific utilities
|
|
26
|
+
| \-- types.ts # Feature types
|
|
27
|
+
|
|
|
28
|
+
|-- hooks/ # Shared hooks
|
|
29
|
+
| |-- use-debounce.ts
|
|
30
|
+
| \-- use-local-storage.ts
|
|
31
|
+
|
|
|
32
|
+
|-- lib/ # Shared utilities
|
|
33
|
+
| |-- api-client.ts # API client setup
|
|
34
|
+
| |-- utils.ts # Helper functions
|
|
35
|
+
| \-- constants.ts # Global constants
|
|
36
|
+
|
|
|
37
|
+
|-- types/ # Shared type definitions
|
|
38
|
+
| \-- index.ts
|
|
39
|
+
|
|
|
40
|
+
\-- styles/ # Global styles
|
|
41
|
+
\-- globals.css
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
---
|
|
@@ -49,23 +49,23 @@ Each feature module is self-contained:
|
|
|
49
49
|
|
|
50
50
|
```
|
|
51
51
|
modules/users/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
52
|
+
|-- components/
|
|
53
|
+
| |-- user-card.tsx # Display component
|
|
54
|
+
| |-- user-form.tsx # Form component
|
|
55
|
+
| \-- user-list.tsx # List component
|
|
56
|
+
|
|
|
57
|
+
|-- hooks/
|
|
58
|
+
| |-- use-user.ts # Single user query
|
|
59
|
+
| |-- use-users.ts # Users list query
|
|
60
|
+
| \-- use-update-user.ts # Update mutation
|
|
61
|
+
|
|
|
62
|
+
|-- context/
|
|
63
|
+
| \-- user-context.tsx # Feature-level state
|
|
64
|
+
|
|
|
65
|
+
|-- lib/
|
|
66
|
+
| \-- user-utils.ts # Feature-specific helpers
|
|
67
|
+
|
|
|
68
|
+
\-- types.ts # Feature types (if not from API)
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
---
|
|
@@ -76,23 +76,23 @@ modules/users/
|
|
|
76
76
|
|
|
77
77
|
```
|
|
78
78
|
components/ui/
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
|-- button.tsx # Base button
|
|
80
|
+
|-- input.tsx # Base input
|
|
81
|
+
|-- select.tsx # Base select
|
|
82
|
+
|-- dialog.tsx # Modal dialog
|
|
83
|
+
|-- dropdown-menu.tsx # Dropdown menu
|
|
84
|
+
\-- index.ts # Barrel export
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
### Common Components (Composites)
|
|
88
88
|
|
|
89
89
|
```
|
|
90
90
|
components/common/
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
|-- header.tsx # App header
|
|
92
|
+
|-- sidebar.tsx # Navigation sidebar
|
|
93
|
+
|-- page-layout.tsx # Page wrapper
|
|
94
|
+
|-- error-boundary.tsx # Error boundary
|
|
95
|
+
\-- loading-spinner.tsx # Loading state
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
---
|
|
@@ -157,12 +157,12 @@ import styles from './user-list.module.css';
|
|
|
157
157
|
## Module Boundaries
|
|
158
158
|
|
|
159
159
|
```
|
|
160
|
-
|
|
160
|
+
[OK] ALLOWED:
|
|
161
161
|
- modules/users/ imports from components/ui/
|
|
162
162
|
- modules/users/ imports from hooks/
|
|
163
163
|
- modules/users/ imports from lib/
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
[X] NOT ALLOWED:
|
|
166
166
|
- modules/users/ imports from modules/orders/
|
|
167
167
|
(Use shared hooks or lift to common location)
|
|
168
168
|
```
|
|
@@ -50,12 +50,12 @@ export function useUsers(page: number, pageSize: number = 20) {
|
|
|
50
50
|
### Query Key Rules
|
|
51
51
|
|
|
52
52
|
```typescript
|
|
53
|
-
//
|
|
53
|
+
// [OK] GOOD: Include all dependencies in query key
|
|
54
54
|
queryKey: ['user', userId]
|
|
55
55
|
queryKey: ['users', { page, pageSize, filter }]
|
|
56
56
|
queryKey: ['posts', userId, 'comments']
|
|
57
57
|
|
|
58
|
-
//
|
|
58
|
+
// [X] BAD: Missing dependencies
|
|
59
59
|
queryKey: ['user'] // Same key for different users!
|
|
60
60
|
queryKey: ['users'] // Same key for different pages!
|
|
61
61
|
```
|
|
@@ -258,15 +258,15 @@ const toggle = useCallback(() => setValue(v => !v), []);
|
|
|
258
258
|
```typescript
|
|
259
259
|
// Don't fetch in useEffect
|
|
260
260
|
useEffect(() => {
|
|
261
|
-
fetch('/api/user').then(...) //
|
|
261
|
+
fetch('/api/user').then(...) // [X] Use useQuery instead
|
|
262
262
|
}, []);
|
|
263
263
|
|
|
264
264
|
// Don't ignore error states
|
|
265
265
|
const { data } = useUser(id);
|
|
266
|
-
return <div>{data.name}</div>; //
|
|
266
|
+
return <div>{data.name}</div>; // [X] data might be undefined
|
|
267
267
|
|
|
268
268
|
// Don't mutate query data directly
|
|
269
|
-
data.name = 'New Name'; //
|
|
269
|
+
data.name = 'New Name'; // [X] Use mutation
|
|
270
270
|
```
|
|
271
271
|
|
|
272
272
|
---
|
|
@@ -36,19 +36,19 @@ This collection of guidelines covers essential patterns and best practices for b
|
|
|
36
36
|
|
|
37
37
|
```
|
|
38
38
|
src/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
|-- components/ # Shared components
|
|
40
|
+
| |-- ui/ # Base UI components
|
|
41
|
+
| \-- common/ # Common composite components
|
|
42
|
+
|
|
|
43
|
+
|-- modules/ # Feature modules
|
|
44
|
+
| \-- {feature}/
|
|
45
|
+
| |-- components/ # Feature-specific components
|
|
46
|
+
| |-- hooks/ # Feature-specific hooks
|
|
47
|
+
| \-- types.ts # Feature types
|
|
48
|
+
|
|
|
49
|
+
|-- hooks/ # Shared hooks
|
|
50
|
+
|-- lib/ # Utilities
|
|
51
|
+
\-- types/ # Shared types
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
### Key Rules
|
|
@@ -26,11 +26,11 @@ pnpm test
|
|
|
26
26
|
### Any Type
|
|
27
27
|
|
|
28
28
|
```typescript
|
|
29
|
-
//
|
|
29
|
+
// [X] FORBIDDEN
|
|
30
30
|
function process(data: any) { ... }
|
|
31
31
|
const result: any = await fetch(...);
|
|
32
32
|
|
|
33
|
-
//
|
|
33
|
+
// [OK] REQUIRED
|
|
34
34
|
function process(data: unknown) {
|
|
35
35
|
if (isValidData(data)) {
|
|
36
36
|
// Now data is typed
|
|
@@ -41,11 +41,11 @@ function process(data: unknown) {
|
|
|
41
41
|
### Non-Null Assertions
|
|
42
42
|
|
|
43
43
|
```typescript
|
|
44
|
-
//
|
|
44
|
+
// [X] FORBIDDEN
|
|
45
45
|
const name = user!.name;
|
|
46
46
|
const element = document.querySelector('.item')!;
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// [OK] REQUIRED
|
|
49
49
|
if (!user) return null;
|
|
50
50
|
const name = user.name;
|
|
51
51
|
|
|
@@ -56,11 +56,11 @@ if (!element) return;
|
|
|
56
56
|
### Type Assertions Without Validation
|
|
57
57
|
|
|
58
58
|
```typescript
|
|
59
|
-
//
|
|
59
|
+
// [X] FORBIDDEN
|
|
60
60
|
const data = response as UserData;
|
|
61
61
|
const config = JSON.parse(str) as Config;
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// [OK] REQUIRED
|
|
64
64
|
const data = userDataSchema.parse(response);
|
|
65
65
|
if (!isUserData(response)) throw new Error('Invalid data');
|
|
66
66
|
```
|
|
@@ -68,11 +68,11 @@ if (!isUserData(response)) throw new Error('Invalid data');
|
|
|
68
68
|
### Div Instead of Semantic HTML
|
|
69
69
|
|
|
70
70
|
```typescript
|
|
71
|
-
//
|
|
71
|
+
// [X] FORBIDDEN
|
|
72
72
|
<div role="button" onClick={handleClick}>Click</div>
|
|
73
73
|
<div onClick={handleNavigate}>Go to page</div>
|
|
74
74
|
|
|
75
|
-
//
|
|
75
|
+
// [OK] REQUIRED
|
|
76
76
|
<button onClick={handleClick}>Click</button>
|
|
77
77
|
<Link href="/page">Go to page</Link>
|
|
78
78
|
```
|
|
@@ -80,10 +80,10 @@ if (!isUserData(response)) throw new Error('Invalid data');
|
|
|
80
80
|
### Native img in Next.js
|
|
81
81
|
|
|
82
82
|
```typescript
|
|
83
|
-
//
|
|
83
|
+
// [X] FORBIDDEN
|
|
84
84
|
<img src="/photo.jpg" alt="Photo" />
|
|
85
85
|
|
|
86
|
-
//
|
|
86
|
+
// [OK] REQUIRED
|
|
87
87
|
import Image from 'next/image';
|
|
88
88
|
<Image src="/photo.jpg" alt="Photo" width={200} height={200} />
|
|
89
89
|
```
|
|
@@ -91,14 +91,14 @@ import Image from 'next/image';
|
|
|
91
91
|
### useEffect for Data Fetching
|
|
92
92
|
|
|
93
93
|
```typescript
|
|
94
|
-
//
|
|
94
|
+
// [X] FORBIDDEN
|
|
95
95
|
useEffect(() => {
|
|
96
96
|
fetch('/api/users')
|
|
97
97
|
.then(res => res.json())
|
|
98
98
|
.then(setUsers);
|
|
99
99
|
}, []);
|
|
100
100
|
|
|
101
|
-
//
|
|
101
|
+
// [OK] REQUIRED
|
|
102
102
|
const { data: users } = useQuery({
|
|
103
103
|
queryKey: ['users'],
|
|
104
104
|
queryFn: () => api.users.list(),
|
|
@@ -112,7 +112,7 @@ const { data: users } = useQuery({
|
|
|
112
112
|
### Handle Loading States
|
|
113
113
|
|
|
114
114
|
```typescript
|
|
115
|
-
//
|
|
115
|
+
// [OK] REQUIRED: Handle all states
|
|
116
116
|
function UserList() {
|
|
117
117
|
const { data, isLoading, error } = useUsers();
|
|
118
118
|
|
|
@@ -127,7 +127,7 @@ function UserList() {
|
|
|
127
127
|
### Accessible Forms
|
|
128
128
|
|
|
129
129
|
```typescript
|
|
130
|
-
//
|
|
130
|
+
// [OK] REQUIRED: Labels for all inputs
|
|
131
131
|
<label htmlFor="email">Email</label>
|
|
132
132
|
<input id="email" type="email" aria-describedby="email-error" />
|
|
133
133
|
{error && <span id="email-error" role="alert">{error}</span>}
|
|
@@ -136,7 +136,7 @@ function UserList() {
|
|
|
136
136
|
### Error Boundaries
|
|
137
137
|
|
|
138
138
|
```typescript
|
|
139
|
-
//
|
|
139
|
+
// [OK] REQUIRED: Wrap feature modules
|
|
140
140
|
<ErrorBoundary fallback={<ErrorFallback />}>
|
|
141
141
|
<FeatureModule />
|
|
142
142
|
</ErrorBoundary>
|
|
@@ -196,32 +196,32 @@ export function UserCard({ user }: Props) {
|
|
|
196
196
|
### Memoization
|
|
197
197
|
|
|
198
198
|
```typescript
|
|
199
|
-
//
|
|
199
|
+
// [OK] Memoize expensive calculations
|
|
200
200
|
const sortedItems = useMemo(
|
|
201
201
|
() => items.sort((a, b) => a.name.localeCompare(b.name)),
|
|
202
202
|
[items]
|
|
203
203
|
);
|
|
204
204
|
|
|
205
|
-
//
|
|
205
|
+
// [OK] Memoize callbacks passed to children
|
|
206
206
|
const handleClick = useCallback(() => {
|
|
207
207
|
doSomething(id);
|
|
208
208
|
}, [id]);
|
|
209
209
|
|
|
210
|
-
//
|
|
210
|
+
// [X] Don't memoize everything - only when needed
|
|
211
211
|
const name = useMemo(() => user.name, [user.name]); // Unnecessary
|
|
212
212
|
```
|
|
213
213
|
|
|
214
214
|
### Avoid Unnecessary Rerenders
|
|
215
215
|
|
|
216
216
|
```typescript
|
|
217
|
-
//
|
|
217
|
+
// [X] BAD: Creates new object every render
|
|
218
218
|
<Component style={{ color: 'red' }} />
|
|
219
219
|
|
|
220
|
-
//
|
|
220
|
+
// [OK] GOOD: Stable reference
|
|
221
221
|
const style = { color: 'red' }; // Outside component
|
|
222
222
|
<Component style={style} />
|
|
223
223
|
|
|
224
|
-
//
|
|
224
|
+
// [OK] GOOD: Or use className
|
|
225
225
|
<Component className="text-red" />
|
|
226
226
|
```
|
|
227
227
|
|
|
@@ -220,12 +220,12 @@ function ProductList() {
|
|
|
220
220
|
|
|
221
221
|
```
|
|
222
222
|
Is the state needed by multiple components?
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
223
|
+
|- No -> Local state (useState)
|
|
224
|
+
\- Yes -> Is it from the server?
|
|
225
|
+
|- Yes -> React Query
|
|
226
|
+
\- No -> Should it persist in URL?
|
|
227
|
+
|- Yes -> URL state (searchParams)
|
|
228
|
+
\- No -> Context or Zustand
|
|
229
229
|
```
|
|
230
230
|
|
|
231
231
|
---
|
|
@@ -235,7 +235,7 @@ Is the state needed by multiple components?
|
|
|
235
235
|
### Don't Sync Server State to Local State
|
|
236
236
|
|
|
237
237
|
```typescript
|
|
238
|
-
//
|
|
238
|
+
// [X] BAD: Duplicating server state
|
|
239
239
|
const { data: users } = useUsers();
|
|
240
240
|
const [localUsers, setLocalUsers] = useState<User[]>([]);
|
|
241
241
|
|
|
@@ -243,7 +243,7 @@ useEffect(() => {
|
|
|
243
243
|
if (users) setLocalUsers(users);
|
|
244
244
|
}, [users]);
|
|
245
245
|
|
|
246
|
-
//
|
|
246
|
+
// [OK] GOOD: Use server state directly
|
|
247
247
|
const { data: users } = useUsers();
|
|
248
248
|
// Use users directly, mutate with mutations
|
|
249
249
|
```
|
|
@@ -251,17 +251,17 @@ const { data: users } = useUsers();
|
|
|
251
251
|
### Don't Overuse Global State
|
|
252
252
|
|
|
253
253
|
```typescript
|
|
254
|
-
//
|
|
254
|
+
// [X] BAD: Everything in global state
|
|
255
255
|
const { isModalOpen, setModalOpen } = useGlobalStore();
|
|
256
256
|
|
|
257
|
-
//
|
|
257
|
+
// [OK] GOOD: Local state for local UI
|
|
258
258
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
259
259
|
```
|
|
260
260
|
|
|
261
261
|
### Don't Put Derived State in State
|
|
262
262
|
|
|
263
263
|
```typescript
|
|
264
|
-
//
|
|
264
|
+
// [X] BAD: Storing derived value
|
|
265
265
|
const [items, setItems] = useState([]);
|
|
266
266
|
const [filteredItems, setFilteredItems] = useState([]);
|
|
267
267
|
|
|
@@ -269,7 +269,7 @@ useEffect(() => {
|
|
|
269
269
|
setFilteredItems(items.filter(i => i.active));
|
|
270
270
|
}, [items]);
|
|
271
271
|
|
|
272
|
-
//
|
|
272
|
+
// [OK] GOOD: Compute derived value
|
|
273
273
|
const [items, setItems] = useState([]);
|
|
274
274
|
const filteredItems = items.filter(i => i.active);
|
|
275
275
|
// Or useMemo for expensive computations
|