@mytechtoday/augment-extensions 0.1.0 → 0.1.2
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/AGENTS.md +83 -3
- package/README.md +6 -5
- package/augment-extensions/coding-standards/python/README.md +44 -0
- package/augment-extensions/coding-standards/python/module.json +26 -0
- package/augment-extensions/coding-standards/python/rules/best-practices.md +232 -0
- package/augment-extensions/coding-standards/python/rules/code-organization.md +220 -0
- package/augment-extensions/coding-standards/python/rules/error-handling.md +221 -0
- package/augment-extensions/coding-standards/python/rules/naming-conventions.md +172 -0
- package/augment-extensions/coding-standards/python/rules/type-hints.md +188 -0
- package/augment-extensions/coding-standards/react/README.md +45 -0
- package/augment-extensions/coding-standards/react/module.json +27 -0
- package/augment-extensions/coding-standards/react/rules/component-patterns.md +214 -0
- package/augment-extensions/coding-standards/react/rules/hooks-best-practices.md +235 -0
- package/augment-extensions/coding-standards/react/rules/performance.md +300 -0
- package/augment-extensions/coding-standards/react/rules/state-management.md +265 -0
- package/augment-extensions/coding-standards/react/rules/typescript-react.md +271 -0
- package/augment-extensions/domain-rules/api-design/README.md +41 -0
- package/augment-extensions/domain-rules/api-design/module.json +27 -0
- package/augment-extensions/domain-rules/api-design/rules/authentication.md +263 -0
- package/augment-extensions/domain-rules/api-design/rules/documentation.md +395 -0
- package/augment-extensions/domain-rules/api-design/rules/error-handling.md +290 -0
- package/augment-extensions/domain-rules/api-design/rules/graphql-api.md +313 -0
- package/augment-extensions/domain-rules/api-design/rules/rest-api.md +214 -0
- package/augment-extensions/domain-rules/api-design/rules/versioning.md +268 -0
- package/augment-extensions/domain-rules/security/README.md +41 -0
- package/augment-extensions/domain-rules/security/module.json +28 -0
- package/augment-extensions/domain-rules/security/rules/authentication-security.md +361 -0
- package/augment-extensions/domain-rules/security/rules/encryption.md +208 -0
- package/augment-extensions/domain-rules/security/rules/input-validation.md +294 -0
- package/augment-extensions/domain-rules/security/rules/owasp-top-10.md +339 -0
- package/augment-extensions/domain-rules/security/rules/secure-coding.md +293 -0
- package/augment-extensions/domain-rules/security/rules/web-security.md +268 -0
- package/augment-extensions/examples/design-patterns/README.md +37 -0
- package/augment-extensions/examples/design-patterns/examples/behavioral-patterns.md +370 -0
- package/augment-extensions/examples/design-patterns/examples/creational-patterns.md +250 -0
- package/augment-extensions/examples/design-patterns/examples/structural-patterns.md +264 -0
- package/augment-extensions/examples/design-patterns/module.json +27 -0
- package/{modules → augment-extensions}/workflows/beads/examples/complete-workflow-example.md +5 -5
- package/{modules → augment-extensions}/workflows/beads/rules/file-format.md +45 -1
- package/{modules → augment-extensions}/workflows/beads/rules/workflow.md +41 -0
- package/{modules → augment-extensions}/workflows/openspec/examples/complete-change-example.md +14 -0
- package/{modules → augment-extensions}/workflows/openspec/rules/spec-format.md +44 -1
- package/{modules → augment-extensions}/workflows/openspec/rules/workflow.md +25 -0
- package/cli/dist/cli.js +69 -1
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/coord.d.ts +30 -0
- package/cli/dist/commands/coord.d.ts.map +1 -0
- package/cli/dist/commands/coord.js +150 -0
- package/cli/dist/commands/coord.js.map +1 -0
- package/cli/dist/commands/link.js +1 -1
- package/cli/dist/commands/link.js.map +1 -1
- package/cli/dist/commands/list.js +1 -1
- package/cli/dist/commands/list.js.map +1 -1
- package/cli/dist/commands/search.d.ts.map +1 -1
- package/cli/dist/commands/search.js +107 -5
- package/cli/dist/commands/search.js.map +1 -1
- package/cli/dist/commands/show.js +1 -1
- package/cli/dist/commands/show.js.map +1 -1
- package/cli/dist/commands/sync.d.ts +26 -0
- package/cli/dist/commands/sync.d.ts.map +1 -0
- package/cli/dist/commands/sync.js +106 -0
- package/cli/dist/commands/sync.js.map +1 -0
- package/cli/dist/commands/update.d.ts.map +1 -1
- package/cli/dist/commands/update.js +132 -7
- package/cli/dist/commands/update.js.map +1 -1
- package/cli/dist/utils/auto-sync.d.ts +34 -0
- package/cli/dist/utils/auto-sync.d.ts.map +1 -0
- package/cli/dist/utils/auto-sync.js +172 -0
- package/cli/dist/utils/auto-sync.js.map +1 -0
- package/cli/dist/utils/beads-sync.d.ts +51 -0
- package/cli/dist/utils/beads-sync.d.ts.map +1 -0
- package/cli/dist/utils/beads-sync.js +171 -0
- package/cli/dist/utils/beads-sync.js.map +1 -0
- package/cli/dist/utils/coordination-queries.d.ts +79 -0
- package/cli/dist/utils/coordination-queries.d.ts.map +1 -0
- package/cli/dist/utils/coordination-queries.js +155 -0
- package/cli/dist/utils/coordination-queries.js.map +1 -0
- package/cli/dist/utils/file-tracking.d.ts +42 -0
- package/cli/dist/utils/file-tracking.d.ts.map +1 -0
- package/cli/dist/utils/file-tracking.js +155 -0
- package/cli/dist/utils/file-tracking.js.map +1 -0
- package/cli/dist/utils/migrate.d.ts +25 -0
- package/cli/dist/utils/migrate.d.ts.map +1 -0
- package/cli/dist/utils/migrate.js +204 -0
- package/cli/dist/utils/migrate.js.map +1 -0
- package/cli/dist/utils/openspec-sync.d.ts +48 -0
- package/cli/dist/utils/openspec-sync.d.ts.map +1 -0
- package/cli/dist/utils/openspec-sync.js +167 -0
- package/cli/dist/utils/openspec-sync.js.map +1 -0
- package/{MODULES.md → modules.md} +1 -1
- package/package.json +9 -7
- /package/{modules → augment-extensions}/coding-standards/typescript/README.md +0 -0
- /package/{modules → augment-extensions}/coding-standards/typescript/module.json +0 -0
- /package/{modules → augment-extensions}/coding-standards/typescript/rules/naming-conventions.md +0 -0
- /package/{modules → augment-extensions}/workflows/beads/README.md +0 -0
- /package/{modules → augment-extensions}/workflows/beads/module.json +0 -0
- /package/{modules → augment-extensions}/workflows/beads/rules/best-practices.md +0 -0
- /package/{modules → augment-extensions}/workflows/beads/rules/manual-setup.md +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/README.md +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/module.json +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/rules/best-practices.md +0 -0
- /package/{modules → augment-extensions}/workflows/openspec/rules/manual-setup.md +0 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# React Performance Optimization
|
|
2
|
+
|
|
3
|
+
Techniques for optimizing React application performance.
|
|
4
|
+
|
|
5
|
+
## React.memo
|
|
6
|
+
|
|
7
|
+
Prevent unnecessary re-renders of components.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Good - Memoize expensive components
|
|
11
|
+
interface UserCardProps {
|
|
12
|
+
user: User;
|
|
13
|
+
onEdit: (id: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const UserCard = React.memo<UserCardProps>(({ user, onEdit }) => {
|
|
17
|
+
return (
|
|
18
|
+
<div className="user-card">
|
|
19
|
+
<h3>{user.name}</h3>
|
|
20
|
+
<button onClick={() => onEdit(user.id)}>Edit</button>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Good - Custom comparison function
|
|
26
|
+
export const UserCard = React.memo<UserCardProps>(
|
|
27
|
+
({ user, onEdit }) => {
|
|
28
|
+
return <div>{user.name}</div>;
|
|
29
|
+
},
|
|
30
|
+
(prevProps, nextProps) => {
|
|
31
|
+
return prevProps.user.id === nextProps.user.id;
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Bad - Memoizing everything unnecessarily
|
|
36
|
+
export const SimpleText = React.memo(({ text }: { text: string }) => {
|
|
37
|
+
return <span>{text}</span>; // Too simple to memoize
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## useCallback
|
|
42
|
+
|
|
43
|
+
Memoize callback functions to prevent child re-renders.
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// Good - Memoize callbacks passed to memoized children
|
|
47
|
+
const ParentComponent: React.FC = () => {
|
|
48
|
+
const [items, setItems] = useState<Item[]>([]);
|
|
49
|
+
|
|
50
|
+
const handleItemClick = useCallback((id: string) => {
|
|
51
|
+
console.log('Clicked:', id);
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
const handleItemDelete = useCallback((id: string) => {
|
|
55
|
+
setItems(prev => prev.filter(item => item.id !== id));
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<>
|
|
60
|
+
{items.map(item => (
|
|
61
|
+
<MemoizedItem
|
|
62
|
+
key={item.id}
|
|
63
|
+
item={item}
|
|
64
|
+
onClick={handleItemClick}
|
|
65
|
+
onDelete={handleItemDelete}
|
|
66
|
+
/>
|
|
67
|
+
))}
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## useMemo
|
|
74
|
+
|
|
75
|
+
Memoize expensive computations.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Good - Memoize expensive calculations
|
|
79
|
+
const ExpensiveComponent: React.FC<{ items: Item[] }> = ({ items }) => {
|
|
80
|
+
const sortedAndFilteredItems = useMemo(() => {
|
|
81
|
+
return items
|
|
82
|
+
.filter(item => item.active)
|
|
83
|
+
.sort((a, b) => a.priority - b.priority);
|
|
84
|
+
}, [items]);
|
|
85
|
+
|
|
86
|
+
const statistics = useMemo(() => {
|
|
87
|
+
return {
|
|
88
|
+
total: items.length,
|
|
89
|
+
active: items.filter(i => i.active).length,
|
|
90
|
+
average: items.reduce((sum, i) => sum + i.value, 0) / items.length
|
|
91
|
+
};
|
|
92
|
+
}, [items]);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
<Stats data={statistics} />
|
|
97
|
+
<ItemList items={sortedAndFilteredItems} />
|
|
98
|
+
</>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Code Splitting
|
|
104
|
+
|
|
105
|
+
Split code to reduce initial bundle size.
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// Good - Lazy load routes
|
|
109
|
+
import { lazy, Suspense } from 'react';
|
|
110
|
+
|
|
111
|
+
const Dashboard = lazy(() => import('./pages/Dashboard'));
|
|
112
|
+
const Settings = lazy(() => import('./pages/Settings'));
|
|
113
|
+
const Profile = lazy(() => import('./pages/Profile'));
|
|
114
|
+
|
|
115
|
+
const App: React.FC = () => (
|
|
116
|
+
<Suspense fallback={<LoadingSpinner />}>
|
|
117
|
+
<Routes>
|
|
118
|
+
<Route path="/dashboard" element={<Dashboard />} />
|
|
119
|
+
<Route path="/settings" element={<Settings />} />
|
|
120
|
+
<Route path="/profile" element={<Profile />} />
|
|
121
|
+
</Routes>
|
|
122
|
+
</Suspense>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Good - Lazy load heavy components
|
|
126
|
+
const HeavyChart = lazy(() => import('./components/HeavyChart'));
|
|
127
|
+
|
|
128
|
+
const Analytics: React.FC = () => (
|
|
129
|
+
<Suspense fallback={<ChartSkeleton />}>
|
|
130
|
+
<HeavyChart data={data} />
|
|
131
|
+
</Suspense>
|
|
132
|
+
);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Virtualization
|
|
136
|
+
|
|
137
|
+
Render only visible items in long lists.
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
// Good - Use react-window for long lists
|
|
141
|
+
import { FixedSizeList } from 'react-window';
|
|
142
|
+
|
|
143
|
+
const VirtualizedList: React.FC<{ items: Item[] }> = ({ items }) => {
|
|
144
|
+
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
|
|
145
|
+
<div style={style}>
|
|
146
|
+
<ItemCard item={items[index]} />
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<FixedSizeList
|
|
152
|
+
height={600}
|
|
153
|
+
itemCount={items.length}
|
|
154
|
+
itemSize={80}
|
|
155
|
+
width="100%"
|
|
156
|
+
>
|
|
157
|
+
{Row}
|
|
158
|
+
</FixedSizeList>
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Debouncing and Throttling
|
|
164
|
+
|
|
165
|
+
Limit expensive operations.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
// Good - Debounce search input
|
|
169
|
+
import { useDebouncedCallback } from 'use-debounce';
|
|
170
|
+
|
|
171
|
+
const SearchInput: React.FC = () => {
|
|
172
|
+
const [query, setQuery] = useState('');
|
|
173
|
+
|
|
174
|
+
const debouncedSearch = useDebouncedCallback(
|
|
175
|
+
(value: string) => {
|
|
176
|
+
performSearch(value);
|
|
177
|
+
},
|
|
178
|
+
500
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
182
|
+
const value = e.target.value;
|
|
183
|
+
setQuery(value);
|
|
184
|
+
debouncedSearch(value);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return <input value={query} onChange={handleChange} />;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Good - Throttle scroll handler
|
|
191
|
+
import { useThrottledCallback } from 'use-debounce';
|
|
192
|
+
|
|
193
|
+
const ScrollTracker: React.FC = () => {
|
|
194
|
+
const throttledScroll = useThrottledCallback(
|
|
195
|
+
() => {
|
|
196
|
+
trackScrollPosition(window.scrollY);
|
|
197
|
+
},
|
|
198
|
+
200
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
window.addEventListener('scroll', throttledScroll);
|
|
203
|
+
return () => window.removeEventListener('scroll', throttledScroll);
|
|
204
|
+
}, [throttledScroll]);
|
|
205
|
+
|
|
206
|
+
return null;
|
|
207
|
+
};
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Avoid Inline Objects and Functions
|
|
211
|
+
|
|
212
|
+
Prevent unnecessary re-renders.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// Bad - Inline objects cause re-renders
|
|
216
|
+
const Parent: React.FC = () => (
|
|
217
|
+
<Child style={{ margin: 10 }} /> // New object every render
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Good - Define outside or memoize
|
|
221
|
+
const childStyle = { margin: 10 };
|
|
222
|
+
const Parent: React.FC = () => (
|
|
223
|
+
<Child style={childStyle} />
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Bad - Inline functions
|
|
227
|
+
const Parent: React.FC = () => (
|
|
228
|
+
<Child onClick={() => console.log('clicked')} /> // New function every render
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
// Good - useCallback
|
|
232
|
+
const Parent: React.FC = () => {
|
|
233
|
+
const handleClick = useCallback(() => {
|
|
234
|
+
console.log('clicked');
|
|
235
|
+
}, []);
|
|
236
|
+
|
|
237
|
+
return <Child onClick={handleClick} />;
|
|
238
|
+
};
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Image Optimization
|
|
242
|
+
|
|
243
|
+
Optimize images for better performance.
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
// Good - Lazy load images
|
|
247
|
+
const LazyImage: React.FC<{ src: string; alt: string }> = ({ src, alt }) => {
|
|
248
|
+
return <img src={src} alt={alt} loading="lazy" />;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Good - Use next/image for Next.js
|
|
252
|
+
import Image from 'next/image';
|
|
253
|
+
|
|
254
|
+
const OptimizedImage: React.FC = () => (
|
|
255
|
+
<Image
|
|
256
|
+
src="/photo.jpg"
|
|
257
|
+
alt="Photo"
|
|
258
|
+
width={500}
|
|
259
|
+
height={300}
|
|
260
|
+
placeholder="blur"
|
|
261
|
+
/>
|
|
262
|
+
);
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Profiling
|
|
266
|
+
|
|
267
|
+
Use React DevTools Profiler to identify performance issues.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
// Good - Wrap components to profile
|
|
271
|
+
import { Profiler } from 'react';
|
|
272
|
+
|
|
273
|
+
const onRenderCallback = (
|
|
274
|
+
id: string,
|
|
275
|
+
phase: 'mount' | 'update',
|
|
276
|
+
actualDuration: number
|
|
277
|
+
) => {
|
|
278
|
+
console.log(`${id} (${phase}) took ${actualDuration}ms`);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const App: React.FC = () => (
|
|
282
|
+
<Profiler id="App" onRender={onRenderCallback}>
|
|
283
|
+
<Dashboard />
|
|
284
|
+
</Profiler>
|
|
285
|
+
);
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Best Practices
|
|
289
|
+
|
|
290
|
+
1. **Profile first** - Use React DevTools before optimizing
|
|
291
|
+
2. **Use React.memo** - For expensive components
|
|
292
|
+
3. **Memoize callbacks** - Use useCallback for child props
|
|
293
|
+
4. **Memoize computations** - Use useMemo for expensive calculations
|
|
294
|
+
5. **Code split** - Lazy load routes and heavy components
|
|
295
|
+
6. **Virtualize lists** - Use react-window for long lists
|
|
296
|
+
7. **Debounce inputs** - Limit expensive operations
|
|
297
|
+
8. **Avoid inline objects** - Define outside or memoize
|
|
298
|
+
9. **Optimize images** - Use lazy loading and optimization
|
|
299
|
+
10. **Monitor bundle size** - Use webpack-bundle-analyzer
|
|
300
|
+
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# React State Management
|
|
2
|
+
|
|
3
|
+
Patterns for managing state in React applications.
|
|
4
|
+
|
|
5
|
+
## Local State (useState)
|
|
6
|
+
|
|
7
|
+
Use for component-specific state.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Good - Local UI state
|
|
11
|
+
const SearchInput: React.FC = () => {
|
|
12
|
+
const [query, setQuery] = useState('');
|
|
13
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<input
|
|
17
|
+
value={query}
|
|
18
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
19
|
+
onFocus={() => setIsFocused(true)}
|
|
20
|
+
onBlur={() => setIsFocused(false)}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Lifting State Up
|
|
27
|
+
|
|
28
|
+
Share state between components by moving it to common parent.
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
// Good - Shared state in parent
|
|
32
|
+
const UserForm: React.FC = () => {
|
|
33
|
+
const [formData, setFormData] = useState({ name: '', email: '' });
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<NameInput
|
|
38
|
+
value={formData.name}
|
|
39
|
+
onChange={(name) => setFormData(prev => ({ ...prev, name }))}
|
|
40
|
+
/>
|
|
41
|
+
<EmailInput
|
|
42
|
+
value={formData.email}
|
|
43
|
+
onChange={(email) => setFormData(prev => ({ ...prev, email }))}
|
|
44
|
+
/>
|
|
45
|
+
<FormPreview data={formData} />
|
|
46
|
+
</>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Context API
|
|
52
|
+
|
|
53
|
+
Use for global or deeply nested state.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// Good - Context for theme
|
|
57
|
+
interface ThemeContextValue {
|
|
58
|
+
theme: 'light' | 'dark';
|
|
59
|
+
toggleTheme: () => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
63
|
+
|
|
64
|
+
export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
65
|
+
const [theme, setTheme] = useState<'light' | 'dark'>('light');
|
|
66
|
+
|
|
67
|
+
const toggleTheme = useCallback(() => {
|
|
68
|
+
setTheme(prev => prev === 'light' ? 'dark' : 'light');
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<ThemeContext.Provider value={value}>
|
|
75
|
+
{children}
|
|
76
|
+
</ThemeContext.Provider>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const useTheme = () => {
|
|
81
|
+
const context = useContext(ThemeContext);
|
|
82
|
+
if (!context) throw new Error('useTheme must be used within ThemeProvider');
|
|
83
|
+
return context;
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## useReducer
|
|
88
|
+
|
|
89
|
+
Use for complex state logic.
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
// Good - Complex state with useReducer
|
|
93
|
+
interface State {
|
|
94
|
+
data: User[];
|
|
95
|
+
loading: boolean;
|
|
96
|
+
error: Error | null;
|
|
97
|
+
page: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
type Action =
|
|
101
|
+
| { type: 'FETCH_START' }
|
|
102
|
+
| { type: 'FETCH_SUCCESS'; payload: User[] }
|
|
103
|
+
| { type: 'FETCH_ERROR'; payload: Error }
|
|
104
|
+
| { type: 'SET_PAGE'; payload: number };
|
|
105
|
+
|
|
106
|
+
const reducer = (state: State, action: Action): State => {
|
|
107
|
+
switch (action.type) {
|
|
108
|
+
case 'FETCH_START':
|
|
109
|
+
return { ...state, loading: true, error: null };
|
|
110
|
+
case 'FETCH_SUCCESS':
|
|
111
|
+
return { ...state, loading: false, data: action.payload };
|
|
112
|
+
case 'FETCH_ERROR':
|
|
113
|
+
return { ...state, loading: false, error: action.payload };
|
|
114
|
+
case 'SET_PAGE':
|
|
115
|
+
return { ...state, page: action.payload };
|
|
116
|
+
default:
|
|
117
|
+
return state;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const UserList: React.FC = () => {
|
|
122
|
+
const [state, dispatch] = useReducer(reducer, {
|
|
123
|
+
data: [],
|
|
124
|
+
loading: false,
|
|
125
|
+
error: null,
|
|
126
|
+
page: 1
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
const fetchUsers = async () => {
|
|
131
|
+
dispatch({ type: 'FETCH_START' });
|
|
132
|
+
try {
|
|
133
|
+
const users = await api.getUsers(state.page);
|
|
134
|
+
dispatch({ type: 'FETCH_SUCCESS', payload: users });
|
|
135
|
+
} catch (error) {
|
|
136
|
+
dispatch({ type: 'FETCH_ERROR', payload: error as Error });
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
fetchUsers();
|
|
141
|
+
}, [state.page]);
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
{state.loading && <Spinner />}
|
|
146
|
+
{state.error && <Error message={state.error.message} />}
|
|
147
|
+
<UserGrid users={state.data} />
|
|
148
|
+
<Pagination
|
|
149
|
+
page={state.page}
|
|
150
|
+
onPageChange={(page) => dispatch({ type: 'SET_PAGE', payload: page })}
|
|
151
|
+
/>
|
|
152
|
+
</>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Zustand (Recommended)
|
|
158
|
+
|
|
159
|
+
Simple, fast state management library.
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
// Good - Zustand store
|
|
163
|
+
import create from 'zustand';
|
|
164
|
+
|
|
165
|
+
interface UserStore {
|
|
166
|
+
users: User[];
|
|
167
|
+
loading: boolean;
|
|
168
|
+
error: Error | null;
|
|
169
|
+
fetchUsers: () => Promise<void>;
|
|
170
|
+
addUser: (user: User) => void;
|
|
171
|
+
removeUser: (id: string) => void;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export const useUserStore = create<UserStore>((set) => ({
|
|
175
|
+
users: [],
|
|
176
|
+
loading: false,
|
|
177
|
+
error: null,
|
|
178
|
+
|
|
179
|
+
fetchUsers: async () => {
|
|
180
|
+
set({ loading: true, error: null });
|
|
181
|
+
try {
|
|
182
|
+
const users = await api.getUsers();
|
|
183
|
+
set({ users, loading: false });
|
|
184
|
+
} catch (error) {
|
|
185
|
+
set({ error: error as Error, loading: false });
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
addUser: (user) => set((state) => ({
|
|
190
|
+
users: [...state.users, user]
|
|
191
|
+
})),
|
|
192
|
+
|
|
193
|
+
removeUser: (id) => set((state) => ({
|
|
194
|
+
users: state.users.filter(u => u.id !== id)
|
|
195
|
+
}))
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
// Usage
|
|
199
|
+
const UserList: React.FC = () => {
|
|
200
|
+
const { users, loading, fetchUsers } = useUserStore();
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
fetchUsers();
|
|
204
|
+
}, [fetchUsers]);
|
|
205
|
+
|
|
206
|
+
return <UserGrid users={users} loading={loading} />;
|
|
207
|
+
};
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## React Query
|
|
211
|
+
|
|
212
|
+
For server state management.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// Good - React Query for data fetching
|
|
216
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
217
|
+
|
|
218
|
+
const useUsers = () => {
|
|
219
|
+
return useQuery({
|
|
220
|
+
queryKey: ['users'],
|
|
221
|
+
queryFn: () => api.getUsers()
|
|
222
|
+
});
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const useCreateUser = () => {
|
|
226
|
+
const queryClient = useQueryClient();
|
|
227
|
+
|
|
228
|
+
return useMutation({
|
|
229
|
+
mutationFn: (user: CreateUserInput) => api.createUser(user),
|
|
230
|
+
onSuccess: () => {
|
|
231
|
+
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Usage
|
|
237
|
+
const UserList: React.FC = () => {
|
|
238
|
+
const { data: users, isLoading, error } = useUsers();
|
|
239
|
+
const createUser = useCreateUser();
|
|
240
|
+
|
|
241
|
+
if (isLoading) return <Spinner />;
|
|
242
|
+
if (error) return <Error message={error.message} />;
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<>
|
|
246
|
+
<UserGrid users={users} />
|
|
247
|
+
<CreateUserForm onSubmit={createUser.mutate} />
|
|
248
|
+
</>
|
|
249
|
+
);
|
|
250
|
+
};
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Best Practices
|
|
254
|
+
|
|
255
|
+
1. **Start with local state** - Use useState for component state
|
|
256
|
+
2. **Lift state up** - When multiple components need it
|
|
257
|
+
3. **Use Context sparingly** - For truly global state (theme, auth)
|
|
258
|
+
4. **Use useReducer** - For complex state logic
|
|
259
|
+
5. **Consider Zustand** - For client state management
|
|
260
|
+
6. **Use React Query** - For server state (data fetching)
|
|
261
|
+
7. **Avoid prop drilling** - Use Context or state management
|
|
262
|
+
8. **Keep state minimal** - Derive values when possible
|
|
263
|
+
9. **Normalize data** - Avoid nested state structures
|
|
264
|
+
10. **Type everything** - Use TypeScript for all state
|
|
265
|
+
|