ghcopilot-hub 1.0.0
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/README.md +176 -0
- package/hub/agents/README.md +243 -0
- package/hub/agents/archiver.agent.md +231 -0
- package/hub/agents/explore.agent.md +49 -0
- package/hub/agents/implementador.agent.md +176 -0
- package/hub/agents/librarian.agent.md +34 -0
- package/hub/agents/momus.agent.md +130 -0
- package/hub/agents/oracle.agent.md +52 -0
- package/hub/agents/plan-guardian.agent.md +109 -0
- package/hub/agents/planificador.agent.md +295 -0
- package/hub/agents/test-sentinel.agent.md +106 -0
- package/hub/base/.github/copilot-instructions.md +10 -0
- package/hub/base/.github/instructions/ghcopilot-hub.instructions.md +6 -0
- package/hub/base/.github/prompts/ghcopilot-hub-maintenance.prompt.md +8 -0
- package/hub/base/.vscode/settings.json +1 -0
- package/hub/packs/base-web.json +4 -0
- package/hub/packs/nextjs-ssr.json +4 -0
- package/hub/packs/node-api.json +4 -0
- package/hub/packs/spa-tanstack.json +4 -0
- package/hub/skills/architecture-testing/SKILL.md +108 -0
- package/hub/skills/architecture-testing/references/archunitts.md +46 -0
- package/hub/skills/ghcopilot-hub-consumer/SKILL.md +115 -0
- package/hub/skills/ghcopilot-hub-consumer/references/workflow.md +39 -0
- package/hub/skills/mermaid-expert/SKILL.md +152 -0
- package/hub/skills/mermaid-expert/assets/examples/c4_model.md +121 -0
- package/hub/skills/mermaid-expert/assets/examples/flowchart.md +123 -0
- package/hub/skills/mermaid-expert/assets/examples/img/base_minimal.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/corporate.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/dark.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/dark_neo.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/default_neo.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/forest_corp.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/handdrawn.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/neo.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/neutral_sketch.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/img/retro.png +0 -0
- package/hub/skills/mermaid-expert/assets/examples/sequence.md +116 -0
- package/hub/skills/mermaid-expert/assets/examples/styles_and_looks.md +102 -0
- package/hub/skills/mermaid-expert/assets/examples/technical.md +130 -0
- package/hub/skills/mermaid-expert/assets/examples.md +57 -0
- package/hub/skills/mermaid-expert/references/cheatsheet.md +88 -0
- package/hub/skills/mermaid-expert/references/validation.md +66 -0
- package/hub/skills/react/SKILL.md +235 -0
- package/hub/skills/react/references/common-mistakes.md +518 -0
- package/hub/skills/react/references/composition-patterns.md +526 -0
- package/hub/skills/react/references/effects-patterns.md +396 -0
- package/hub/skills/react/references/react-compiler.md +268 -0
- package/hub/skills/react-hook-form/SKILL.md +291 -0
- package/hub/skills/react-hook-form/references/field-arrays.md +98 -0
- package/hub/skills/react-hook-form/references/integration.md +102 -0
- package/hub/skills/react-hook-form/references/performance.md +96 -0
- package/hub/skills/skill-creator/SKILL.md +152 -0
- package/hub/skills/skill-creator/assets/SKILL-TEMPLATE.md +84 -0
- package/hub/skills/skill-judge/README.md +261 -0
- package/hub/skills/skill-judge/SKILL.md +806 -0
- package/hub/skills/tailwind/SKILL.md +200 -0
- package/hub/skills/tanstack/SKILL.md +284 -0
- package/hub/skills/tanstack/references/loader-adapter-examples.md +79 -0
- package/hub/skills/tanstack/references/query-options-examples.md +115 -0
- package/hub/skills/tanstack/references/resilience-patterns.md +110 -0
- package/hub/skills/tanstack/references/suspense-consumption-examples.md +82 -0
- package/hub/skills/tanstack-query/SKILL.md +241 -0
- package/hub/skills/tanstack-query/references/advanced-hooks.md +126 -0
- package/hub/skills/tanstack-query/references/best-practices.md +241 -0
- package/hub/skills/tanstack-query/references/cache-strategies.md +474 -0
- package/hub/skills/tanstack-query/references/common-patterns.md +239 -0
- package/hub/skills/tanstack-query/references/migration-v5.md +93 -0
- package/hub/skills/tanstack-query/references/resilience-and-mutations.md +63 -0
- package/hub/skills/tanstack-query/references/testing.md +116 -0
- package/hub/skills/tanstack-query/references/top-errors.md +148 -0
- package/hub/skills/tanstack-query/references/typescript.md +176 -0
- package/hub/skills/tanstack-router/SKILL.md +145 -0
- package/hub/skills/tanstack-router/references/code-splitting.md +31 -0
- package/hub/skills/tanstack-router/references/errors-and-boundaries.md +44 -0
- package/hub/skills/tanstack-router/references/loaders-and-preload.md +51 -0
- package/hub/skills/tanstack-router/references/navigation.md +24 -0
- package/hub/skills/tanstack-router/references/private-routes.md +169 -0
- package/hub/skills/tanstack-router/references/router-context.md +35 -0
- package/hub/skills/tanstack-router/references/search-params.md +29 -0
- package/hub/skills/tanstack-router/references/typescript.md +24 -0
- package/hub/skills/testing/SKILL.md +187 -0
- package/hub/skills/testing/references/assertions.md +64 -0
- package/hub/skills/testing/references/async-testing.md +66 -0
- package/hub/skills/testing/references/e2e-strategy.md +69 -0
- package/hub/skills/testing/references/layer-matrix.md +67 -0
- package/hub/skills/testing/references/performance.md +49 -0
- package/hub/skills/testing/references/tooling-map.md +81 -0
- package/hub/skills/testing/references/zustand-mocking.md +84 -0
- package/hub/skills/typescript/SKILL.md +232 -0
- package/hub/skills/typescript/references/perf-additional-concerns.md +248 -0
- package/hub/skills/typescript/references/perf-execution-cache-locality.md +178 -0
- package/hub/skills/typescript/references/reduce-branching.md +147 -0
- package/hub/skills/typescript/references/reduce-looping.md +203 -0
- package/hub/skills/typescript/references/style-and-types.md +171 -0
- package/hub/skills/typescript/references/type-vs-interface.md +27 -0
- package/hub/skills/zod/SKILL.md +219 -0
- package/hub/skills/zustand/SKILL.md +273 -0
- package/package.json +59 -0
- package/tooling/cli/src/bin.js +11 -0
- package/tooling/cli/src/cli.js +409 -0
- package/tooling/cli/src/lib/catalog-loader.js +191 -0
- package/tooling/cli/src/lib/constants.js +39 -0
- package/tooling/cli/src/lib/errors.js +8 -0
- package/tooling/cli/src/lib/frontmatter.js +41 -0
- package/tooling/cli/src/lib/fs-utils.js +77 -0
- package/tooling/cli/src/lib/managed-header.js +74 -0
- package/tooling/cli/src/lib/manifest.js +105 -0
- package/tooling/cli/src/lib/resolver.js +53 -0
- package/tooling/cli/src/lib/sync-engine.js +262 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
# Common React Mistakes
|
|
2
|
+
|
|
3
|
+
Frequent errors and their fixes.
|
|
4
|
+
|
|
5
|
+
## Quick Navigation
|
|
6
|
+
|
|
7
|
+
| Symptom | Section |
|
|
8
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| Renders `0` unexpectedly | [Conditional Rendering with &&](#conditional-rendering-with-) |
|
|
10
|
+
| Ref misuse during render | [Reading/Writing Refs During Render](#readingwriting-refs-during-render) |
|
|
11
|
+
| Impossible state combinations | [Impossible States](#impossible-states) |
|
|
12
|
+
| Redundant / poorly shaped state | [State Principles](#state-principles) |
|
|
13
|
+
| Hook naming / lifecycle wrappers | [Custom Hooks](#custom-hooks) |
|
|
14
|
+
| Derived/reset logic in effects | [Effects for Derived State](#effects-for-derived-state) and [Effects to Reset State on Prop Change](#effects-to-reset-state-on-prop-change) |
|
|
15
|
+
|
|
16
|
+
## Conditional Rendering with &&
|
|
17
|
+
|
|
18
|
+
Using a number on the left side of `&&` can render "0" instead of nothing.
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
// ❌ BUG: Renders "0" when array is empty
|
|
22
|
+
function UserList({ users }: { users: User[] }) {
|
|
23
|
+
return <div>{users.length && users.map((user) => <UserCard key={user.id} user={user} />)}</div>;
|
|
24
|
+
}
|
|
25
|
+
// When users.length is 0, React renders the number 0
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// ✅ FIX: Use explicit comparison
|
|
30
|
+
function UserList({ users }: { users: User[] }) {
|
|
31
|
+
return (
|
|
32
|
+
<div>{users.length > 0 && users.map((user) => <UserCard key={user.id} user={user} />)}</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
// ✅ ALSO VALID: Ternary operator
|
|
39
|
+
function UserList({ users }: { users: User[] }) {
|
|
40
|
+
return (
|
|
41
|
+
<div>
|
|
42
|
+
{users.length > 0 ? (
|
|
43
|
+
users.map((user) => <UserCard key={user.id} user={user} />)
|
|
44
|
+
) : (
|
|
45
|
+
<EmptyState message="No users found" />
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Rule**: Never use values that could be `0`, `""`, `NaN` on the left of `&&`.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Reading/Writing Refs During Render
|
|
57
|
+
|
|
58
|
+
Refs are mutable and accessing them during render breaks React's rendering model.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// ❌ BUG: Writing ref during render
|
|
62
|
+
function Counter() {
|
|
63
|
+
const renderCount = useRef(0);
|
|
64
|
+
renderCount.current += 1; // Side effect during render!
|
|
65
|
+
|
|
66
|
+
return <div>Rendered {renderCount.current} times</div>;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// ✅ FIX: Use state for render-related values
|
|
72
|
+
function Counter() {
|
|
73
|
+
const [renderCount, setRenderCount] = useState(0);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
setRenderCount((prev) => prev + 1);
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
return <div>Rendered {renderCount} times</div>;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// ✅ FIX: Use refs only in effects/handlers
|
|
85
|
+
function VideoPlayer({ src }: { src: string }) {
|
|
86
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
87
|
+
|
|
88
|
+
// ❌ NEVER: Read during render
|
|
89
|
+
// const isPlaying = !videoRef.current?.paused;
|
|
90
|
+
|
|
91
|
+
// ✅ Read in event handler
|
|
92
|
+
const handleToggle = () => {
|
|
93
|
+
if (videoRef.current?.paused) {
|
|
94
|
+
videoRef.current.play();
|
|
95
|
+
} else {
|
|
96
|
+
videoRef.current?.pause();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return <video ref={videoRef} src={src} />;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## State Updates Based on Previous State
|
|
107
|
+
|
|
108
|
+
Always use the functional update form when new state depends on previous state.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// ❌ BUG: May use stale state in rapid updates
|
|
112
|
+
function Counter() {
|
|
113
|
+
const [count, setCount] = useState(0);
|
|
114
|
+
|
|
115
|
+
const handleClick = () => {
|
|
116
|
+
setCount(count + 1);
|
|
117
|
+
setCount(count + 1); // Both use same stale `count`
|
|
118
|
+
// Result: count increases by 1, not 2
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// ✅ FIX: Use functional updates
|
|
125
|
+
function Counter() {
|
|
126
|
+
const [count, setCount] = useState(0);
|
|
127
|
+
|
|
128
|
+
const handleClick = () => {
|
|
129
|
+
setCount((prev) => prev + 1);
|
|
130
|
+
setCount((prev) => prev + 1); // Uses updated value
|
|
131
|
+
// Result: count increases by 2
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Impossible States
|
|
139
|
+
|
|
140
|
+
Multiple booleans can represent invalid combinations. Prefer a single status union.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
// ❌ BUG: Impossible combinations are representable
|
|
144
|
+
function SearchInput() {
|
|
145
|
+
const [isTyping, setIsTyping] = useState(false);
|
|
146
|
+
const [isEmpty, setIsEmpty] = useState(true);
|
|
147
|
+
// isTyping=true and isEmpty=true can happen
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ✅ FIX: Single status source of truth
|
|
151
|
+
type InputStatus = "empty" | "typing" | "submitted";
|
|
152
|
+
|
|
153
|
+
function SearchInput() {
|
|
154
|
+
const [status, setStatus] = useState<InputStatus>("empty");
|
|
155
|
+
|
|
156
|
+
const isEmpty = status === "empty";
|
|
157
|
+
const isTyping = status === "typing";
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Same strategy applies to async flows:
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
type FetchStatus = "idle" | "pending" | "success" | "error";
|
|
165
|
+
|
|
166
|
+
function useAsyncData() {
|
|
167
|
+
const [status, setStatus] = useState<FetchStatus>("idle");
|
|
168
|
+
|
|
169
|
+
const isLoading = status === "idle" || status === "pending";
|
|
170
|
+
const isSuccess = status === "success";
|
|
171
|
+
const isError = status === "error";
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## State Principles
|
|
178
|
+
|
|
179
|
+
### Group Related State
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
// ❌ BUG: Separate state that changes together
|
|
183
|
+
const [x, setX] = useState(0);
|
|
184
|
+
const [y, setY] = useState(0);
|
|
185
|
+
|
|
186
|
+
// ✅ FIX: Group in one object
|
|
187
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Avoid Redundant State
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
// ❌ BUG: Derived value duplicated in state
|
|
194
|
+
const [firstName, setFirstName] = useState("");
|
|
195
|
+
const [lastName, setLastName] = useState("");
|
|
196
|
+
const [fullName, setFullName] = useState("");
|
|
197
|
+
|
|
198
|
+
// ✅ FIX: Derive during render
|
|
199
|
+
const fullName = `${firstName} ${lastName}`;
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Reset with `key`
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
function App() {
|
|
206
|
+
const [formKey, setFormKey] = useState(0);
|
|
207
|
+
const resetForm = () => setFormKey((prev) => prev + 1);
|
|
208
|
+
|
|
209
|
+
return <Form key={formKey} />;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Colocate State
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
// ❌ BUG: State too high, unrelated subtree re-renders
|
|
217
|
+
function App() {
|
|
218
|
+
const [firstName, setFirstName] = useState("");
|
|
219
|
+
return (
|
|
220
|
+
<>
|
|
221
|
+
<input value={firstName} onChange={(e) => setFirstName(e.target.value)} />
|
|
222
|
+
<PageContent />
|
|
223
|
+
</>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ✅ FIX: Move state to the leaf that owns it
|
|
228
|
+
function App() {
|
|
229
|
+
return (
|
|
230
|
+
<>
|
|
231
|
+
<SignupForm />
|
|
232
|
+
<PageContent />
|
|
233
|
+
</>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Mutating State Directly
|
|
241
|
+
|
|
242
|
+
React only re-renders when state reference changes. Mutating objects/arrays doesn't trigger updates.
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
// ❌ BUG: Mutating array doesn't trigger re-render
|
|
246
|
+
function TodoList() {
|
|
247
|
+
const [todos, setTodos] = useState<Todo[]>([]);
|
|
248
|
+
|
|
249
|
+
const addTodo = (todo: Todo) => {
|
|
250
|
+
todos.push(todo); // Mutation!
|
|
251
|
+
setTodos(todos); // Same reference, no re-render
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
// ✅ FIX: Create new reference
|
|
258
|
+
function TodoList() {
|
|
259
|
+
const [todos, setTodos] = useState<Todo[]>([]);
|
|
260
|
+
|
|
261
|
+
const addTodo = (todo: Todo) => {
|
|
262
|
+
setTodos((prev) => [...prev, todo]); // New array
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const updateTodo = (id: string, updates: Partial<Todo>) => {
|
|
266
|
+
setTodos((prev) => prev.map((todo) => (todo.id === id ? { ...todo, ...updates } : todo)));
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const removeTodo = (id: string) => {
|
|
270
|
+
setTodos((prev) => prev.filter((todo) => todo.id !== id));
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Missing Key or Using Index as Key
|
|
278
|
+
|
|
279
|
+
Keys help React identify which items changed. Bad keys cause bugs and performance issues.
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
// ❌ BUG: Using index as key with reorderable/filterable lists
|
|
283
|
+
{
|
|
284
|
+
todos.map((todo, index) => <TodoItem key={index} todo={todo} />);
|
|
285
|
+
}
|
|
286
|
+
// Problem: Reordering shuffles state between components
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
// ❌ BUG: Missing key
|
|
291
|
+
{
|
|
292
|
+
todos.map((todo) => (
|
|
293
|
+
<TodoItem todo={todo} /> // Warning: Each child should have a unique "key"
|
|
294
|
+
));
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
```tsx
|
|
299
|
+
// ✅ FIX: Use stable, unique identifier
|
|
300
|
+
{
|
|
301
|
+
todos.map((todo) => <TodoItem key={todo.id} todo={todo} />);
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**When index as key is OK**:
|
|
306
|
+
|
|
307
|
+
- Static lists that never reorder
|
|
308
|
+
- Items have no state
|
|
309
|
+
- List is never filtered
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Custom Hooks
|
|
314
|
+
|
|
315
|
+
### Hooks Share Logic, Not State
|
|
316
|
+
|
|
317
|
+
Each hook call has isolated state.
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
function StatusBar() {
|
|
321
|
+
const isOnline = useOnlineStatus();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function SaveButton() {
|
|
325
|
+
const isOnline = useOnlineStatus();
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Name `useXxx` Only If It Uses Hooks
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
// ❌ BUG: Misleading name, no hooks inside
|
|
333
|
+
function useSorted(items: string[]) {
|
|
334
|
+
return items.slice().sort();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ✅ FIX: plain utility name
|
|
338
|
+
function getSorted(items: string[]) {
|
|
339
|
+
return items.slice().sort();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ✅ Valid useXxx
|
|
343
|
+
function useAuth() {
|
|
344
|
+
return useContext(AuthContext);
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Avoid Lifecycle Wrapper Hooks
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
// ❌ BUG: Hides dependencies from linter
|
|
352
|
+
function useMount(fn: () => void) {
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
fn();
|
|
355
|
+
}, []);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ✅ FIX: Keep dependency checks explicit at call site
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
doSomething();
|
|
361
|
+
}, [doSomething]);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Effects for Derived State
|
|
367
|
+
|
|
368
|
+
Don't use Effects to compute values that can be calculated during render.
|
|
369
|
+
|
|
370
|
+
```tsx
|
|
371
|
+
// ❌ BUG: Effect for derived state
|
|
372
|
+
const [firstName, setFirstName] = useState("Taylor");
|
|
373
|
+
const [lastName, setLastName] = useState("Swift");
|
|
374
|
+
const [fullName, setFullName] = useState("");
|
|
375
|
+
|
|
376
|
+
useEffect(() => {
|
|
377
|
+
setFullName(firstName + " " + lastName);
|
|
378
|
+
}, [firstName, lastName]);
|
|
379
|
+
|
|
380
|
+
// ✅ FIX: Calculate during render
|
|
381
|
+
const [firstName, setFirstName] = useState("Taylor");
|
|
382
|
+
const [lastName, setLastName] = useState("Swift");
|
|
383
|
+
const fullName = firstName + " " + lastName;
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Effects to Reset State on Prop Change
|
|
389
|
+
|
|
390
|
+
Don't use Effects to reset state when props change. Use the `key` prop.
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
// ❌ BUG: Effect to reset state
|
|
394
|
+
function ProfilePage({ userId }: { userId: string }) {
|
|
395
|
+
const [comment, setComment] = useState("");
|
|
396
|
+
|
|
397
|
+
useEffect(() => {
|
|
398
|
+
setComment("");
|
|
399
|
+
}, [userId]);
|
|
400
|
+
// ...
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ✅ FIX: Use key to reset component state
|
|
404
|
+
function ProfilePage({ userId }: { userId: string }) {
|
|
405
|
+
return <Profile userId={userId} key={userId} />;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function Profile({ userId }: { userId: string }) {
|
|
409
|
+
const [comment, setComment] = useState(""); // Resets automatically
|
|
410
|
+
// ...
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## useEffect for Data Fetching
|
|
417
|
+
|
|
418
|
+
Don't use `useEffect` for data fetching in components. Use TanStack Query with loaders.
|
|
419
|
+
|
|
420
|
+
```tsx
|
|
421
|
+
// ❌ ANTI-PATTERN: useEffect for data fetching
|
|
422
|
+
function ProductList() {
|
|
423
|
+
const [products, setProducts] = useState<Product[]>([]);
|
|
424
|
+
const [loading, setLoading] = useState(true);
|
|
425
|
+
|
|
426
|
+
useEffect(() => {
|
|
427
|
+
fetchProducts().then((data) => {
|
|
428
|
+
setProducts(data);
|
|
429
|
+
setLoading(false);
|
|
430
|
+
});
|
|
431
|
+
}, []);
|
|
432
|
+
|
|
433
|
+
if (loading) return <Skeleton />;
|
|
434
|
+
return <List products={products} />;
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
// ✅ PATTERN: Use TanStack Query with Suspense (per project architecture)
|
|
440
|
+
// 1. Define query in application layer
|
|
441
|
+
// application/product/product.queries.ts
|
|
442
|
+
export const productQueries = {
|
|
443
|
+
list: () =>
|
|
444
|
+
queryOptions({
|
|
445
|
+
queryKey: ["products"],
|
|
446
|
+
queryFn: () => productRepository.getAll(),
|
|
447
|
+
}),
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// 2. Load in router loader
|
|
451
|
+
// interface/router/routes/product/productList.route.tsx
|
|
452
|
+
export const productListRoute = createRoute({
|
|
453
|
+
path: "/products",
|
|
454
|
+
loader: () => queryClient.ensureQueryData(productQueries.list()),
|
|
455
|
+
component: ProductListPage,
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// 3. Consume with Suspense query
|
|
459
|
+
// presentation/product/ProductList.page.tsx
|
|
460
|
+
function ProductListPage() {
|
|
461
|
+
const { data: products } = useSuspenseQuery(productQueries.list());
|
|
462
|
+
return <ProductList products={products} />;
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Overusing Context
|
|
469
|
+
|
|
470
|
+
Don't use React Context for frequently changing values. It re-renders all consumers.
|
|
471
|
+
|
|
472
|
+
```tsx
|
|
473
|
+
// ❌ PERFORMANCE ISSUE: Context for frequently changing state
|
|
474
|
+
const MousePositionContext = createContext({ x: 0, y: 0 });
|
|
475
|
+
|
|
476
|
+
function App() {
|
|
477
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
478
|
+
|
|
479
|
+
return (
|
|
480
|
+
<MousePositionContext.Provider value={position}>
|
|
481
|
+
{/* ALL consumers re-render on every mouse move */}
|
|
482
|
+
<div onMouseMove={(e) => setPosition({ x: e.clientX, y: e.clientY })}>
|
|
483
|
+
<Header />
|
|
484
|
+
<Content />
|
|
485
|
+
<Footer />
|
|
486
|
+
</div>
|
|
487
|
+
</MousePositionContext.Provider>
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
```tsx
|
|
493
|
+
// ✅ FIX: Use Zustand for frequently changing client state
|
|
494
|
+
// application/shared/store/mouse.store.ts
|
|
495
|
+
export const useMouseStore = create<MouseState>()((set) => ({
|
|
496
|
+
position: { x: 0, y: 0 },
|
|
497
|
+
setPosition: (x: number, y: number) => set({ position: { x, y } }),
|
|
498
|
+
}));
|
|
499
|
+
|
|
500
|
+
// Components subscribe to specific slices
|
|
501
|
+
function Cursor() {
|
|
502
|
+
const position = useMouseStore((state) => state.position);
|
|
503
|
+
return <div style={{ left: position.x, top: position.y }} />;
|
|
504
|
+
}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
**Use Context for**:
|
|
508
|
+
|
|
509
|
+
- Themes
|
|
510
|
+
- Locale/i18n
|
|
511
|
+
- Auth state (rarely changes)
|
|
512
|
+
- Feature flags
|
|
513
|
+
|
|
514
|
+
**Use Zustand for**:
|
|
515
|
+
|
|
516
|
+
- Frequently updating values
|
|
517
|
+
- Complex client state
|
|
518
|
+
- State shared across many components
|