omgkit 2.2.0 → 2.3.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.
Files changed (60) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/plugin/skills/databases/database-management/SKILL.md +288 -0
  4. package/plugin/skills/databases/database-migration/SKILL.md +285 -0
  5. package/plugin/skills/databases/database-schema-design/SKILL.md +195 -0
  6. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  7. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  8. package/plugin/skills/databases/redis/SKILL.md +53 -860
  9. package/plugin/skills/databases/supabase/SKILL.md +283 -0
  10. package/plugin/skills/devops/aws/SKILL.md +68 -672
  11. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  12. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  13. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  14. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  15. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  16. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  17. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  18. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  19. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  20. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  21. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  23. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  26. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  27. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  28. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  29. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  30. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  31. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  32. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  33. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  34. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  35. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  36. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  37. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  38. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  39. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  40. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  41. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  42. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  43. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  44. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  45. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  46. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  47. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  48. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  49. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  50. package/plugin/skills/security/oauth/SKILL.md +80 -934
  51. package/plugin/skills/security/owasp/SKILL.md +78 -862
  52. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  53. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  54. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  55. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  56. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  57. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  58. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  59. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  60. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,1325 +1,132 @@
1
1
  ---
2
- name: frontend-design
3
- description: Frontend design patterns with compound components, render props, custom hooks, state machines, and scalable architecture
4
- category: frontend
5
- triggers:
6
- - frontend design
7
- - component patterns
8
- - design patterns
9
- - compound components
10
- - render props
11
- - custom hooks
12
- - state management
13
- - component architecture
2
+ name: designing-frontend-patterns
3
+ description: Claude designs scalable React component architectures using compound components, custom hooks, and state machines. Use when building reusable UI systems or complex component APIs.
14
4
  ---
15
5
 
16
- # Frontend Design Patterns
6
+ # Designing Frontend Patterns
17
7
 
18
- Enterprise-grade **frontend design patterns** following industry best practices. This skill covers compound components, render props, custom hooks, state machines, higher-order components, and scalable architecture patterns used by top engineering teams.
19
-
20
- ## Purpose
21
-
22
- Build maintainable and scalable frontend applications:
23
-
24
- - Design reusable component APIs with compound patterns
25
- - Implement flexible data fetching with render props
26
- - Create powerful custom hooks for shared logic
27
- - Manage complex state with state machines
28
- - Build accessible form systems
29
- - Implement optimistic UI updates
30
- - Design scalable component architecture
31
-
32
- ## Features
33
-
34
- ### 1. Compound Components Pattern
8
+ ## Quick Start
35
9
 
36
10
  ```tsx
37
- // components/Select/SelectContext.tsx
38
- import { createContext, useContext, useState, useCallback, ReactNode } from 'react';
39
-
40
- interface SelectContextValue {
41
- isOpen: boolean;
42
- selectedValue: string | null;
43
- selectedLabel: string | null;
44
- highlightedIndex: number;
45
- open: () => void;
46
- close: () => void;
47
- toggle: () => void;
48
- selectOption: (value: string, label: string) => void;
49
- setHighlightedIndex: (index: number) => void;
50
- }
51
-
11
+ // Compound component pattern with context
52
12
  const SelectContext = createContext<SelectContextValue | null>(null);
53
13
 
54
- function useSelectContext() {
55
- const context = useContext(SelectContext);
56
- if (!context) {
57
- throw new Error('Select components must be used within a Select provider');
58
- }
59
- return context;
60
- }
61
-
62
- // components/Select/Select.tsx
63
- interface SelectProps {
64
- children: ReactNode;
65
- defaultValue?: string;
66
- value?: string;
67
- onValueChange?: (value: string) => void;
68
- disabled?: boolean;
69
- }
70
-
71
- export function Select({
72
- children,
73
- defaultValue,
74
- value: controlledValue,
75
- onValueChange,
76
- disabled = false,
77
- }: SelectProps) {
14
+ export function Select({ children, value, onValueChange }: SelectProps) {
78
15
  const [isOpen, setIsOpen] = useState(false);
79
- const [internalValue, setInternalValue] = useState(defaultValue ?? null);
80
- const [selectedLabel, setSelectedLabel] = useState<string | null>(null);
81
- const [highlightedIndex, setHighlightedIndex] = useState(-1);
82
-
83
- const isControlled = controlledValue !== undefined;
84
- const selectedValue = isControlled ? controlledValue : internalValue;
85
-
86
- const open = useCallback(() => {
87
- if (!disabled) setIsOpen(true);
88
- }, [disabled]);
89
-
90
- const close = useCallback(() => {
91
- setIsOpen(false);
92
- setHighlightedIndex(-1);
93
- }, []);
94
-
95
- const toggle = useCallback(() => {
96
- if (!disabled) setIsOpen((prev) => !prev);
97
- }, [disabled]);
98
-
99
- const selectOption = useCallback(
100
- (value: string, label: string) => {
101
- if (!isControlled) {
102
- setInternalValue(value);
103
- }
104
- setSelectedLabel(label);
105
- onValueChange?.(value);
106
- close();
107
- },
108
- [isControlled, onValueChange, close]
109
- );
110
-
111
16
  return (
112
- <SelectContext.Provider
113
- value={{
114
- isOpen,
115
- selectedValue,
116
- selectedLabel,
117
- highlightedIndex,
118
- open,
119
- close,
120
- toggle,
121
- selectOption,
122
- setHighlightedIndex,
123
- }}
124
- >
125
- <div className="relative inline-block" data-disabled={disabled}>
126
- {children}
127
- </div>
17
+ <SelectContext.Provider value={{ isOpen, setIsOpen, value, onValueChange }}>
18
+ <div className="relative">{children}</div>
128
19
  </SelectContext.Provider>
129
20
  );
130
21
  }
131
22
 
132
- // components/Select/SelectTrigger.tsx
133
- interface SelectTriggerProps {
134
- children?: ReactNode;
135
- placeholder?: string;
136
- className?: string;
137
- }
138
-
139
- export function SelectTrigger({
140
- children,
141
- placeholder = 'Select an option',
142
- className = '',
143
- }: SelectTriggerProps) {
144
- const { isOpen, selectedLabel, toggle } = useSelectContext();
145
-
146
- return (
147
- <button
148
- type="button"
149
- role="combobox"
150
- aria-expanded={isOpen}
151
- aria-haspopup="listbox"
152
- className={`flex items-center justify-between gap-2 px-3 py-2 border rounded-md
153
- bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500
154
- ${className}`}
155
- onClick={toggle}
156
- >
157
- <span className={selectedLabel ? 'text-gray-900' : 'text-gray-500'}>
158
- {selectedLabel || children || placeholder}
159
- </span>
160
- <ChevronIcon isOpen={isOpen} />
161
- </button>
162
- );
163
- }
164
-
165
- // components/Select/SelectContent.tsx
166
- interface SelectContentProps {
167
- children: ReactNode;
168
- className?: string;
169
- }
170
-
171
- export function SelectContent({ children, className = '' }: SelectContentProps) {
172
- const { isOpen, close } = useSelectContext();
173
- const contentRef = useRef<HTMLDivElement>(null);
174
-
175
- // Close on outside click
176
- useEffect(() => {
177
- const handleClickOutside = (event: MouseEvent) => {
178
- if (contentRef.current && !contentRef.current.contains(event.target as Node)) {
179
- close();
180
- }
181
- };
182
-
183
- if (isOpen) {
184
- document.addEventListener('mousedown', handleClickOutside);
185
- return () => document.removeEventListener('mousedown', handleClickOutside);
186
- }
187
- }, [isOpen, close]);
188
-
189
- // Close on escape
190
- useEffect(() => {
191
- const handleEscape = (event: KeyboardEvent) => {
192
- if (event.key === 'Escape') close();
193
- };
194
-
195
- if (isOpen) {
196
- document.addEventListener('keydown', handleEscape);
197
- return () => document.removeEventListener('keydown', handleEscape);
198
- }
199
- }, [isOpen, close]);
200
-
201
- if (!isOpen) return null;
202
-
203
- return (
204
- <div
205
- ref={contentRef}
206
- role="listbox"
207
- className={`absolute z-50 mt-1 w-full bg-white border rounded-md shadow-lg
208
- max-h-60 overflow-auto ${className}`}
209
- >
210
- {children}
211
- </div>
212
- );
213
- }
214
-
215
- // components/Select/SelectItem.tsx
216
- interface SelectItemProps {
217
- value: string;
218
- children: ReactNode;
219
- disabled?: boolean;
220
- }
221
-
222
- export function SelectItem({ value, children, disabled = false }: SelectItemProps) {
223
- const { selectedValue, selectOption } = useSelectContext();
224
- const isSelected = selectedValue === value;
225
-
226
- const handleSelect = () => {
227
- if (!disabled) {
228
- selectOption(value, typeof children === 'string' ? children : value);
229
- }
230
- };
231
-
232
- return (
233
- <div
234
- role="option"
235
- aria-selected={isSelected}
236
- aria-disabled={disabled}
237
- className={`px-3 py-2 cursor-pointer
238
- ${isSelected ? 'bg-blue-100 text-blue-900' : 'text-gray-900'}
239
- ${disabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-100'}
240
- `}
241
- onClick={handleSelect}
242
- >
243
- {children}
244
- </div>
245
- );
246
- }
247
-
248
- // Usage example
249
- function SelectExample() {
250
- const [country, setCountry] = useState('');
251
-
252
- return (
253
- <Select value={country} onValueChange={setCountry}>
254
- <SelectTrigger placeholder="Select a country" />
255
- <SelectContent>
256
- <SelectItem value="us">United States</SelectItem>
257
- <SelectItem value="uk">United Kingdom</SelectItem>
258
- <SelectItem value="ca">Canada</SelectItem>
259
- <SelectItem value="au" disabled>Australia (Coming Soon)</SelectItem>
260
- </SelectContent>
261
- </Select>
262
- );
263
- }
23
+ Select.Trigger = SelectTrigger;
24
+ Select.Content = SelectContent;
25
+ Select.Item = SelectItem;
264
26
  ```
265
27
 
266
- ### 2. Render Props Pattern
267
-
268
- ```tsx
269
- // components/DataFetcher.tsx
270
- import { useState, useEffect, useCallback, ReactNode } from 'react';
271
-
272
- interface FetchState<T> {
273
- data: T | null;
274
- loading: boolean;
275
- error: Error | null;
276
- refetch: () => Promise<void>;
277
- }
278
-
279
- interface DataFetcherProps<T> {
280
- url: string;
281
- options?: RequestInit;
282
- children: (state: FetchState<T>) => ReactNode;
283
- onSuccess?: (data: T) => void;
284
- onError?: (error: Error) => void;
285
- initialData?: T;
286
- enabled?: boolean;
287
- }
288
-
289
- export function DataFetcher<T>({
290
- url,
291
- options,
292
- children,
293
- onSuccess,
294
- onError,
295
- initialData = null as T,
296
- enabled = true,
297
- }: DataFetcherProps<T>) {
298
- const [data, setData] = useState<T | null>(initialData);
299
- const [loading, setLoading] = useState(false);
300
- const [error, setError] = useState<Error | null>(null);
301
-
302
- const fetchData = useCallback(async () => {
303
- if (!enabled) return;
304
-
305
- setLoading(true);
306
- setError(null);
307
-
308
- try {
309
- const response = await fetch(url, {
310
- ...options,
311
- headers: {
312
- 'Content-Type': 'application/json',
313
- ...options?.headers,
314
- },
315
- });
316
-
317
- if (!response.ok) {
318
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
319
- }
320
-
321
- const result = await response.json();
322
- setData(result);
323
- onSuccess?.(result);
324
- } catch (err) {
325
- const error = err instanceof Error ? err : new Error(String(err));
326
- setError(error);
327
- onError?.(error);
328
- } finally {
329
- setLoading(false);
330
- }
331
- }, [url, options, enabled, onSuccess, onError]);
332
-
333
- useEffect(() => {
334
- fetchData();
335
- }, [fetchData]);
336
-
337
- return <>{children({ data, loading, error, refetch: fetchData })}</>;
338
- }
339
-
340
- // Mouse position render props
341
- interface MousePosition {
342
- x: number;
343
- y: number;
344
- }
345
-
346
- interface MouseTrackerProps {
347
- children: (position: MousePosition) => ReactNode;
348
- }
349
-
350
- export function MouseTracker({ children }: MouseTrackerProps) {
351
- const [position, setPosition] = useState<MousePosition>({ x: 0, y: 0 });
352
-
353
- useEffect(() => {
354
- const handleMouseMove = (event: MouseEvent) => {
355
- setPosition({ x: event.clientX, y: event.clientY });
356
- };
357
-
358
- window.addEventListener('mousemove', handleMouseMove);
359
- return () => window.removeEventListener('mousemove', handleMouseMove);
360
- }, []);
361
-
362
- return <>{children(position)}</>;
363
- }
364
-
365
- // Intersection observer render props
366
- interface IntersectionProps {
367
- children: (entry: IntersectionObserverEntry | null, ref: React.RefObject<Element>) => ReactNode;
368
- options?: IntersectionObserverInit;
369
- }
370
-
371
- export function IntersectionObserverRender({ children, options }: IntersectionProps) {
372
- const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null);
373
- const ref = useRef<Element>(null);
374
-
375
- useEffect(() => {
376
- const element = ref.current;
377
- if (!element) return;
378
-
379
- const observer = new IntersectionObserver(([entry]) => {
380
- setEntry(entry);
381
- }, options);
382
-
383
- observer.observe(element);
384
- return () => observer.disconnect();
385
- }, [options]);
386
-
387
- return <>{children(entry, ref)}</>;
388
- }
28
+ ## Features
389
29
 
390
- // Usage examples
391
- function RenderPropsExamples() {
392
- return (
393
- <>
394
- {/* Data fetcher */}
395
- <DataFetcher<User[]> url="/api/users">
396
- {({ data, loading, error, refetch }) => {
397
- if (loading) return <Spinner />;
398
- if (error) return <ErrorMessage error={error} onRetry={refetch} />;
399
- return <UserList users={data || []} />;
400
- }}
401
- </DataFetcher>
30
+ | Feature | Description | Guide |
31
+ |---------|-------------|-------|
32
+ | Compound Components | Shared state via context for flexible component APIs | `ref/compound-components.md` |
33
+ | Custom Hooks | Encapsulate reusable logic (useDebounce, useLocalStorage) | `ref/custom-hooks.md` |
34
+ | Render Props | Maximum flexibility for data fetching and rendering | `ref/render-props.md` |
35
+ | State Machines | Predictable state transitions for complex flows | `ref/state-machines.md` |
36
+ | HOCs | Cross-cutting concerns (auth, error boundaries) | `ref/higher-order-components.md` |
37
+ | Optimistic UI | Instant feedback with rollback on failure | `ref/optimistic-updates.md` |
402
38
 
403
- {/* Mouse tracker */}
404
- <MouseTracker>
405
- {({ x, y }) => (
406
- <div className="fixed pointer-events-none" style={{ left: x, top: y }}>
407
- Custom cursor at ({x}, {y})
408
- </div>
409
- )}
410
- </MouseTracker>
39
+ ## Common Patterns
411
40
 
412
- {/* Lazy loading with intersection observer */}
413
- <IntersectionObserverRender options={{ threshold: 0.1 }}>
414
- {(entry, ref) => (
415
- <div ref={ref as React.RefObject<HTMLDivElement>}>
416
- {entry?.isIntersecting ? <HeavyComponent /> : <Placeholder />}
417
- </div>
418
- )}
419
- </IntersectionObserverRender>
420
- </>
421
- );
422
- }
423
- ```
424
-
425
- ### 3. Custom Hooks Pattern
41
+ ### Custom Hook with Cleanup
426
42
 
427
43
  ```tsx
428
- // hooks/useLocalStorage.ts
429
- import { useState, useEffect, useCallback } from 'react';
430
-
431
- export function useLocalStorage<T>(
432
- key: string,
433
- initialValue: T
434
- ): [T, (value: T | ((prev: T) => T)) => void, () => void] {
435
- // Get initial value from localStorage or use provided initial
436
- const [storedValue, setStoredValue] = useState<T>(() => {
437
- if (typeof window === 'undefined') return initialValue;
438
-
439
- try {
440
- const item = window.localStorage.getItem(key);
441
- return item ? JSON.parse(item) : initialValue;
442
- } catch (error) {
443
- console.warn(`Error reading localStorage key "${key}":`, error);
444
- return initialValue;
445
- }
446
- });
447
-
448
- // Persist to localStorage when value changes
449
- useEffect(() => {
450
- if (typeof window === 'undefined') return;
451
-
452
- try {
453
- window.localStorage.setItem(key, JSON.stringify(storedValue));
454
- } catch (error) {
455
- console.warn(`Error setting localStorage key "${key}":`, error);
456
- }
457
- }, [key, storedValue]);
458
-
459
- // Listen for changes in other tabs/windows
460
- useEffect(() => {
461
- const handleStorageChange = (event: StorageEvent) => {
462
- if (event.key === key && event.newValue) {
463
- try {
464
- setStoredValue(JSON.parse(event.newValue));
465
- } catch {
466
- // Ignore parse errors
467
- }
468
- }
469
- };
470
-
471
- window.addEventListener('storage', handleStorageChange);
472
- return () => window.removeEventListener('storage', handleStorageChange);
473
- }, [key]);
474
-
475
- const remove = useCallback(() => {
476
- if (typeof window !== 'undefined') {
477
- window.localStorage.removeItem(key);
478
- setStoredValue(initialValue);
479
- }
480
- }, [key, initialValue]);
481
-
482
- return [storedValue, setStoredValue, remove];
483
- }
484
-
485
- // hooks/useDebounce.ts
486
44
  export function useDebounce<T>(value: T, delay: number): T {
487
45
  const [debouncedValue, setDebouncedValue] = useState(value);
488
46
 
489
47
  useEffect(() => {
490
- const timer = setTimeout(() => {
491
- setDebouncedValue(value);
492
- }, delay);
493
-
48
+ const timer = setTimeout(() => setDebouncedValue(value), delay);
494
49
  return () => clearTimeout(timer);
495
50
  }, [value, delay]);
496
51
 
497
52
  return debouncedValue;
498
53
  }
499
54
 
500
- // hooks/useDebouncedCallback.ts
501
- export function useDebouncedCallback<T extends (...args: any[]) => any>(
502
- callback: T,
503
- delay: number
504
- ): (...args: Parameters<T>) => void {
505
- const timeoutRef = useRef<NodeJS.Timeout | null>(null);
506
- const callbackRef = useRef(callback);
507
-
508
- // Keep callback ref updated
509
- useEffect(() => {
510
- callbackRef.current = callback;
511
- }, [callback]);
512
-
513
- // Cleanup on unmount
514
- useEffect(() => {
515
- return () => {
516
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
517
- };
518
- }, []);
519
-
520
- return useCallback(
521
- (...args: Parameters<T>) => {
522
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
523
-
524
- timeoutRef.current = setTimeout(() => {
525
- callbackRef.current(...args);
526
- }, delay);
527
- },
528
- [delay]
529
- );
530
- }
531
-
532
- // hooks/useAsync.ts
533
- interface AsyncState<T> {
534
- data: T | null;
535
- loading: boolean;
536
- error: Error | null;
537
- }
538
-
539
- interface UseAsyncReturn<T> extends AsyncState<T> {
540
- execute: () => Promise<T | null>;
541
- reset: () => void;
542
- }
543
-
544
- export function useAsync<T>(
545
- asyncFunction: () => Promise<T>,
546
- immediate = true
547
- ): UseAsyncReturn<T> {
548
- const [state, setState] = useState<AsyncState<T>>({
549
- data: null,
550
- loading: immediate,
551
- error: null,
552
- });
553
-
554
- const execute = useCallback(async () => {
555
- setState((prev) => ({ ...prev, loading: true, error: null }));
556
-
55
+ export function useLocalStorage<T>(key: string, initialValue: T) {
56
+ const [stored, setStored] = useState<T>(() => {
557
57
  try {
558
- const data = await asyncFunction();
559
- setState({ data, loading: false, error: null });
560
- return data;
561
- } catch (error) {
562
- setState({
563
- data: null,
564
- loading: false,
565
- error: error instanceof Error ? error : new Error(String(error)),
566
- });
567
- return null;
568
- }
569
- }, [asyncFunction]);
570
-
571
- const reset = useCallback(() => {
572
- setState({ data: null, loading: false, error: null });
573
- }, []);
574
-
575
- useEffect(() => {
576
- if (immediate) {
577
- execute();
578
- }
579
- }, [execute, immediate]);
580
-
581
- return { ...state, execute, reset };
582
- }
583
-
584
- // hooks/useMediaQuery.ts
585
- export function useMediaQuery(query: string): boolean {
586
- const [matches, setMatches] = useState(() => {
587
- if (typeof window === 'undefined') return false;
588
- return window.matchMedia(query).matches;
58
+ const item = window.localStorage.getItem(key);
59
+ return item ? JSON.parse(item) : initialValue;
60
+ } catch { return initialValue; }
589
61
  });
590
62
 
591
63
  useEffect(() => {
592
- const mediaQuery = window.matchMedia(query);
593
- const handler = (event: MediaQueryListEvent) => setMatches(event.matches);
594
-
595
- // Set initial value
596
- setMatches(mediaQuery.matches);
597
-
598
- // Modern browsers
599
- mediaQuery.addEventListener('change', handler);
600
- return () => mediaQuery.removeEventListener('change', handler);
601
- }, [query]);
602
-
603
- return matches;
604
- }
605
-
606
- // hooks/useClickOutside.ts
607
- export function useClickOutside<T extends HTMLElement>(
608
- callback: () => void
609
- ): React.RefObject<T> {
610
- const ref = useRef<T>(null);
611
-
612
- useEffect(() => {
613
- const handleClick = (event: MouseEvent) => {
614
- if (ref.current && !ref.current.contains(event.target as Node)) {
615
- callback();
616
- }
617
- };
618
-
619
- document.addEventListener('mousedown', handleClick);
620
- return () => document.removeEventListener('mousedown', handleClick);
621
- }, [callback]);
64
+ window.localStorage.setItem(key, JSON.stringify(stored));
65
+ }, [key, stored]);
622
66
 
623
- return ref;
624
- }
625
-
626
- // hooks/usePrevious.ts
627
- export function usePrevious<T>(value: T): T | undefined {
628
- const ref = useRef<T>();
629
-
630
- useEffect(() => {
631
- ref.current = value;
632
- }, [value]);
633
-
634
- return ref.current;
635
- }
636
-
637
- // Usage examples
638
- function HooksExamples() {
639
- const [theme, setTheme] = useLocalStorage('theme', 'light');
640
- const [search, setSearch] = useState('');
641
- const debouncedSearch = useDebounce(search, 300);
642
- const isMobile = useMediaQuery('(max-width: 768px)');
643
- const { data, loading, error } = useAsync(() => fetchUsers());
644
-
645
- const dropdownRef = useClickOutside<HTMLDivElement>(() => {
646
- setIsOpen(false);
647
- });
648
-
649
- return (
650
- <div>
651
- <ThemeToggle theme={theme} onChange={setTheme} />
652
- <SearchInput value={search} onChange={setSearch} />
653
- <SearchResults query={debouncedSearch} />
654
- {isMobile ? <MobileNav /> : <DesktopNav />}
655
- </div>
656
- );
67
+ return [stored, setStored] as const;
657
68
  }
658
69
  ```
659
70
 
660
- ### 4. State Machine Pattern
71
+ ### State Machine Pattern
661
72
 
662
73
  ```tsx
663
- // lib/createStateMachine.ts
664
- type StateConfig<TContext, TState extends string, TEvent extends { type: string }> = {
665
- initial: TState;
666
- context: TContext;
667
- states: {
668
- [K in TState]: {
669
- on?: {
670
- [E in TEvent['type']]?: TState | {
671
- target: TState;
672
- actions?: (context: TContext, event: Extract<TEvent, { type: E }>) => TContext;
673
- guard?: (context: TContext, event: Extract<TEvent, { type: E }>) => boolean;
674
- };
675
- };
676
- entry?: (context: TContext) => TContext | void;
677
- exit?: (context: TContext) => TContext | void;
678
- };
679
- };
680
- };
681
-
682
- // hooks/useStateMachine.ts
683
- export function useStateMachine<
684
- TContext,
685
- TState extends string,
686
- TEvent extends { type: string }
687
- >(config: StateConfig<TContext, TState, TEvent>) {
688
- const [state, setState] = useState<TState>(config.initial);
689
- const [context, setContext] = useState<TContext>(config.context);
690
-
691
- const send = useCallback(
692
- (event: TEvent) => {
693
- const currentStateConfig = config.states[state];
694
- const transition = currentStateConfig.on?.[event.type as TEvent['type']];
695
-
696
- if (!transition) return;
697
-
698
- let nextState: TState;
699
- let newContext = context;
700
-
701
- if (typeof transition === 'string') {
702
- nextState = transition;
703
- } else {
704
- // Check guard condition
705
- if (transition.guard && !transition.guard(context, event as any)) {
706
- return;
707
- }
708
- nextState = transition.target;
709
- if (transition.actions) {
710
- newContext = transition.actions(context, event as any);
711
- }
712
- }
713
-
714
- // Run exit action
715
- const exitAction = currentStateConfig.exit;
716
- if (exitAction) {
717
- const result = exitAction(newContext);
718
- if (result) newContext = result;
719
- }
720
-
721
- // Run entry action
722
- const nextStateConfig = config.states[nextState];
723
- const entryAction = nextStateConfig.entry;
724
- if (entryAction) {
725
- const result = entryAction(newContext);
726
- if (result) newContext = result;
727
- }
728
-
729
- setState(nextState);
730
- setContext(newContext);
731
- },
732
- [state, context, config]
733
- );
734
-
735
- const matches = useCallback((s: TState) => state === s, [state]);
736
-
737
- return { state, context, send, matches };
738
- }
739
-
740
- // Example: Form submission state machine
741
74
  type FormState = 'idle' | 'validating' | 'submitting' | 'success' | 'error';
742
-
743
75
  type FormEvent =
744
76
  | { type: 'SUBMIT'; data: FormData }
745
- | { type: 'VALIDATE_SUCCESS' }
746
- | { type: 'VALIDATE_ERROR'; errors: Record<string, string> }
747
- | { type: 'SUBMIT_SUCCESS'; response: any }
748
- | { type: 'SUBMIT_ERROR'; error: string }
749
- | { type: 'RESET' };
77
+ | { type: 'SUCCESS'; response: any }
78
+ | { type: 'ERROR'; error: string };
750
79
 
751
- interface FormContext {
752
- data: FormData | null;
753
- errors: Record<string, string>;
754
- response: any;
755
- submitError: string | null;
756
- }
80
+ function useFormMachine() {
81
+ const [state, setState] = useState<FormState>('idle');
82
+ const [context, setContext] = useState({ data: null, error: null });
757
83
 
758
- const formMachine: StateConfig<FormContext, FormState, FormEvent> = {
759
- initial: 'idle',
760
- context: {
761
- data: null,
762
- errors: {},
763
- response: null,
764
- submitError: null,
765
- },
766
- states: {
767
- idle: {
768
- on: {
769
- SUBMIT: {
770
- target: 'validating',
771
- actions: (ctx, event) => ({ ...ctx, data: event.data, errors: {} }),
772
- },
773
- },
774
- },
775
- validating: {
776
- on: {
777
- VALIDATE_SUCCESS: 'submitting',
778
- VALIDATE_ERROR: {
779
- target: 'idle',
780
- actions: (ctx, event) => ({ ...ctx, errors: event.errors }),
781
- },
782
- },
783
- },
784
- submitting: {
785
- on: {
786
- SUBMIT_SUCCESS: {
787
- target: 'success',
788
- actions: (ctx, event) => ({ ...ctx, response: event.response }),
789
- },
790
- SUBMIT_ERROR: {
791
- target: 'error',
792
- actions: (ctx, event) => ({ ...ctx, submitError: event.error }),
793
- },
794
- },
795
- },
796
- success: {
797
- on: {
798
- RESET: {
799
- target: 'idle',
800
- actions: () => ({
801
- data: null,
802
- errors: {},
803
- response: null,
804
- submitError: null,
805
- }),
806
- },
807
- },
808
- },
809
- error: {
810
- on: {
811
- SUBMIT: {
812
- target: 'validating',
813
- actions: (ctx, event) => ({
814
- ...ctx,
815
- data: event.data,
816
- submitError: null,
817
- }),
818
- },
819
- RESET: {
820
- target: 'idle',
821
- actions: () => ({
822
- data: null,
823
- errors: {},
824
- response: null,
825
- submitError: null,
826
- }),
827
- },
828
- },
829
- },
830
- },
831
- };
832
-
833
- function FormWithStateMachine() {
834
- const { state, context, send, matches } = useStateMachine(formMachine);
835
-
836
- const handleSubmit = async (formData: FormData) => {
837
- send({ type: 'SUBMIT', data: formData });
838
-
839
- // Validate
840
- const errors = validate(formData);
841
- if (Object.keys(errors).length > 0) {
842
- send({ type: 'VALIDATE_ERROR', errors });
843
- return;
84
+ const send = useCallback((event: FormEvent) => {
85
+ switch (state) {
86
+ case 'idle':
87
+ if (event.type === 'SUBMIT') { setState('validating'); }
88
+ break;
89
+ case 'submitting':
90
+ if (event.type === 'SUCCESS') { setState('success'); }
91
+ if (event.type === 'ERROR') { setState('error'); setContext(c => ({ ...c, error: event.error })); }
92
+ break;
844
93
  }
845
- send({ type: 'VALIDATE_SUCCESS' });
94
+ }, [state]);
846
95
 
847
- // Submit
848
- try {
849
- const response = await submitForm(formData);
850
- send({ type: 'SUBMIT_SUCCESS', response });
851
- } catch (error) {
852
- send({ type: 'SUBMIT_ERROR', error: error.message });
853
- }
854
- };
855
-
856
- return (
857
- <form onSubmit={(e) => { e.preventDefault(); handleSubmit(new FormData(e.target)); }}>
858
- {matches('success') && <SuccessMessage response={context.response} />}
859
- {matches('error') && <ErrorMessage error={context.submitError} />}
860
- {Object.entries(context.errors).map(([field, error]) => (
861
- <FieldError key={field} field={field} error={error} />
862
- ))}
863
- <button type="submit" disabled={matches('submitting') || matches('validating')}>
864
- {matches('submitting') ? 'Submitting...' : 'Submit'}
865
- </button>
866
- </form>
867
- );
96
+ return { state, context, send };
868
97
  }
869
98
  ```
870
99
 
871
- ### 5. Higher-Order Components (HOC)
100
+ ### Optimistic Update Hook
872
101
 
873
102
  ```tsx
874
- // hocs/withAuth.tsx
875
- import { useRouter } from 'next/router';
876
- import { ComponentType, useEffect } from 'react';
877
- import { useAuth } from '@/hooks/useAuth';
878
-
879
- interface WithAuthOptions {
880
- redirectTo?: string;
881
- requiredRole?: string;
882
- }
883
-
884
- export function withAuth<P extends object>(
885
- WrappedComponent: ComponentType<P>,
886
- options: WithAuthOptions = {}
887
- ) {
888
- const { redirectTo = '/login', requiredRole } = options;
889
-
890
- function AuthenticatedComponent(props: P) {
891
- const router = useRouter();
892
- const { user, loading, isAuthenticated } = useAuth();
893
-
894
- useEffect(() => {
895
- if (!loading && !isAuthenticated) {
896
- router.push(`${redirectTo}?returnUrl=${encodeURIComponent(router.asPath)}`);
897
- }
898
-
899
- if (!loading && requiredRole && user?.role !== requiredRole) {
900
- router.push('/unauthorized');
901
- }
902
- }, [loading, isAuthenticated, user, router]);
903
-
904
- if (loading) {
905
- return <LoadingSpinner />;
906
- }
907
-
908
- if (!isAuthenticated) {
909
- return null;
910
- }
911
-
912
- if (requiredRole && user?.role !== requiredRole) {
913
- return null;
914
- }
915
-
916
- return <WrappedComponent {...props} />;
917
- }
918
-
919
- AuthenticatedComponent.displayName = `withAuth(${
920
- WrappedComponent.displayName || WrappedComponent.name || 'Component'
921
- })`;
922
-
923
- return AuthenticatedComponent;
924
- }
925
-
926
- // hocs/withErrorBoundary.tsx
927
- interface WithErrorBoundaryOptions {
928
- fallback?: ReactNode;
929
- onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
930
- }
931
-
932
- export function withErrorBoundary<P extends object>(
933
- WrappedComponent: ComponentType<P>,
934
- options: WithErrorBoundaryOptions = {}
935
- ) {
936
- const { fallback, onError } = options;
937
-
938
- class ErrorBoundaryHOC extends React.Component<
939
- P,
940
- { hasError: boolean; error: Error | null }
941
- > {
942
- static displayName = `withErrorBoundary(${
943
- WrappedComponent.displayName || WrappedComponent.name || 'Component'
944
- })`;
945
-
946
- constructor(props: P) {
947
- super(props);
948
- this.state = { hasError: false, error: null };
949
- }
950
-
951
- static getDerivedStateFromError(error: Error) {
952
- return { hasError: true, error };
953
- }
954
-
955
- componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
956
- onError?.(error, errorInfo);
957
- }
958
-
959
- render() {
960
- if (this.state.hasError) {
961
- return fallback || <DefaultErrorFallback error={this.state.error} />;
962
- }
103
+ export function useOptimistic<T>(initialData: T, reducer: (state: T, action: any) => T) {
104
+ const [state, setState] = useState({ data: initialData, pending: false, error: null });
105
+ const previousRef = useRef(initialData);
963
106
 
964
- return <WrappedComponent {...this.props} />;
965
- }
966
- }
967
-
968
- return ErrorBoundaryHOC;
969
- }
970
-
971
- // hocs/withLoading.tsx
972
- interface WithLoadingProps {
973
- isLoading?: boolean;
974
- }
107
+ const optimisticUpdate = useCallback(async (action: any, asyncOp: () => Promise<T>) => {
108
+ previousRef.current = state.data;
109
+ setState({ data: reducer(state.data, action), pending: true, error: null });
975
110
 
976
- export function withLoading<P extends object>(
977
- WrappedComponent: ComponentType<P>,
978
- LoadingComponent: ComponentType = DefaultSpinner
979
- ) {
980
- function LoadingWrapper(props: P & WithLoadingProps) {
981
- const { isLoading, ...rest } = props;
982
-
983
- if (isLoading) {
984
- return <LoadingComponent />;
111
+ try {
112
+ const result = await asyncOp();
113
+ setState({ data: result, pending: false, error: null });
114
+ } catch (error) {
115
+ setState({ data: previousRef.current, pending: false, error: error as Error });
985
116
  }
986
-
987
- return <WrappedComponent {...(rest as P)} />;
988
- }
989
-
990
- LoadingWrapper.displayName = `withLoading(${
991
- WrappedComponent.displayName || WrappedComponent.name || 'Component'
992
- })`;
993
-
994
- return LoadingWrapper;
995
- }
996
-
997
- // Usage
998
- const ProtectedDashboard = withAuth(Dashboard, { requiredRole: 'admin' });
999
- const SafeUserProfile = withErrorBoundary(UserProfile, {
1000
- fallback: <div>Something went wrong</div>,
1001
- });
1002
- const LoadableDataGrid = withLoading(DataGrid);
1003
- ```
1004
-
1005
- ### 6. Optimistic UI Updates
1006
-
1007
- ```tsx
1008
- // hooks/useOptimistic.ts
1009
- interface OptimisticState<T> {
1010
- data: T;
1011
- pending: boolean;
1012
- error: Error | null;
1013
- }
1014
-
1015
- export function useOptimistic<T, TAction>(
1016
- initialData: T,
1017
- reducer: (state: T, action: TAction) => T
1018
- ) {
1019
- const [state, setState] = useState<OptimisticState<T>>({
1020
- data: initialData,
1021
- pending: false,
1022
- error: null,
1023
- });
1024
-
1025
- const previousDataRef = useRef<T>(initialData);
1026
-
1027
- const optimisticUpdate = useCallback(
1028
- async (action: TAction, asyncOperation: () => Promise<T>) => {
1029
- // Store previous state for rollback
1030
- previousDataRef.current = state.data;
1031
-
1032
- // Apply optimistic update
1033
- const optimisticData = reducer(state.data, action);
1034
- setState({ data: optimisticData, pending: true, error: null });
1035
-
1036
- try {
1037
- // Perform actual async operation
1038
- const result = await asyncOperation();
1039
- setState({ data: result, pending: false, error: null });
1040
- return result;
1041
- } catch (error) {
1042
- // Rollback on error
1043
- setState({
1044
- data: previousDataRef.current,
1045
- pending: false,
1046
- error: error instanceof Error ? error : new Error(String(error)),
1047
- });
1048
- throw error;
1049
- }
1050
- },
1051
- [state.data, reducer]
1052
- );
117
+ }, [state.data, reducer]);
1053
118
 
1054
119
  return { ...state, optimisticUpdate };
1055
120
  }
1056
-
1057
- // Example: Todo list with optimistic updates
1058
- interface Todo {
1059
- id: string;
1060
- text: string;
1061
- completed: boolean;
1062
- }
1063
-
1064
- function TodoList() {
1065
- const { data: todos, pending, error, optimisticUpdate } = useOptimistic<
1066
- Todo[],
1067
- { type: 'ADD' | 'TOGGLE' | 'DELETE'; payload: any }
1068
- >(initialTodos, (state, action) => {
1069
- switch (action.type) {
1070
- case 'ADD':
1071
- return [...state, action.payload];
1072
- case 'TOGGLE':
1073
- return state.map((todo) =>
1074
- todo.id === action.payload
1075
- ? { ...todo, completed: !todo.completed }
1076
- : todo
1077
- );
1078
- case 'DELETE':
1079
- return state.filter((todo) => todo.id !== action.payload);
1080
- default:
1081
- return state;
1082
- }
1083
- });
1084
-
1085
- const addTodo = async (text: string) => {
1086
- const tempId = `temp-${Date.now()}`;
1087
- const newTodo = { id: tempId, text, completed: false };
1088
-
1089
- await optimisticUpdate(
1090
- { type: 'ADD', payload: newTodo },
1091
- async () => {
1092
- const response = await api.createTodo({ text });
1093
- return todos.map((t) => (t.id === tempId ? response : t));
1094
- }
1095
- );
1096
- };
1097
-
1098
- const toggleTodo = async (id: string) => {
1099
- await optimisticUpdate(
1100
- { type: 'TOGGLE', payload: id },
1101
- async () => {
1102
- await api.toggleTodo(id);
1103
- return todos.map((t) =>
1104
- t.id === id ? { ...t, completed: !t.completed } : t
1105
- );
1106
- }
1107
- );
1108
- };
1109
-
1110
- return (
1111
- <div className={pending ? 'opacity-70' : ''}>
1112
- {error && <ErrorToast message={error.message} />}
1113
- <TodoForm onSubmit={addTodo} disabled={pending} />
1114
- {todos.map((todo) => (
1115
- <TodoItem
1116
- key={todo.id}
1117
- todo={todo}
1118
- onToggle={() => toggleTodo(todo.id)}
1119
- />
1120
- ))}
1121
- </div>
1122
- );
1123
- }
1124
- ```
1125
-
1126
- ## Use Cases
1127
-
1128
- ### Component Library Architecture
1129
-
1130
- ```tsx
1131
- // lib/components/index.ts - Barrel exports
1132
- export { Button, type ButtonProps } from './Button';
1133
- export { Input, type InputProps } from './Input';
1134
- export { Select, SelectTrigger, SelectContent, SelectItem } from './Select';
1135
-
1136
- // lib/components/Button/Button.tsx
1137
- import { forwardRef, ButtonHTMLAttributes } from 'react';
1138
- import { cva, type VariantProps } from 'class-variance-authority';
1139
- import { cn } from '@/lib/utils';
1140
-
1141
- const buttonVariants = cva(
1142
- 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
1143
- {
1144
- variants: {
1145
- variant: {
1146
- default: 'bg-primary text-primary-foreground hover:bg-primary/90',
1147
- destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
1148
- outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
1149
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
1150
- ghost: 'hover:bg-accent hover:text-accent-foreground',
1151
- link: 'text-primary underline-offset-4 hover:underline',
1152
- },
1153
- size: {
1154
- default: 'h-10 px-4 py-2',
1155
- sm: 'h-9 rounded-md px-3',
1156
- lg: 'h-11 rounded-md px-8',
1157
- icon: 'h-10 w-10',
1158
- },
1159
- },
1160
- defaultVariants: {
1161
- variant: 'default',
1162
- size: 'default',
1163
- },
1164
- }
1165
- );
1166
-
1167
- export interface ButtonProps
1168
- extends ButtonHTMLAttributes<HTMLButtonElement>,
1169
- VariantProps<typeof buttonVariants> {
1170
- loading?: boolean;
1171
- }
1172
-
1173
- export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
1174
- ({ className, variant, size, loading, children, disabled, ...props }, ref) => {
1175
- return (
1176
- <button
1177
- className={cn(buttonVariants({ variant, size, className }))}
1178
- ref={ref}
1179
- disabled={disabled || loading}
1180
- {...props}
1181
- >
1182
- {loading && <Spinner className="mr-2 h-4 w-4" />}
1183
- {children}
1184
- </button>
1185
- );
1186
- }
1187
- );
1188
-
1189
- Button.displayName = 'Button';
1190
- ```
1191
-
1192
- ### Form Architecture Pattern
1193
-
1194
- ```tsx
1195
- // hooks/useForm.ts
1196
- interface UseFormConfig<T> {
1197
- initialValues: T;
1198
- validate: (values: T) => Partial<Record<keyof T, string>>;
1199
- onSubmit: (values: T) => Promise<void>;
1200
- }
1201
-
1202
- export function useForm<T extends Record<string, any>>({
1203
- initialValues,
1204
- validate,
1205
- onSubmit,
1206
- }: UseFormConfig<T>) {
1207
- const [values, setValues] = useState<T>(initialValues);
1208
- const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
1209
- const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({});
1210
- const [submitting, setSubmitting] = useState(false);
1211
-
1212
- const handleChange = useCallback(
1213
- (field: keyof T) => (e: React.ChangeEvent<HTMLInputElement>) => {
1214
- const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
1215
- setValues((prev) => ({ ...prev, [field]: value }));
1216
- },
1217
- []
1218
- );
1219
-
1220
- const handleBlur = useCallback(
1221
- (field: keyof T) => () => {
1222
- setTouched((prev) => ({ ...prev, [field]: true }));
1223
- const fieldErrors = validate(values);
1224
- if (fieldErrors[field]) {
1225
- setErrors((prev) => ({ ...prev, [field]: fieldErrors[field] }));
1226
- } else {
1227
- setErrors((prev) => {
1228
- const { [field]: _, ...rest } = prev;
1229
- return rest as Partial<Record<keyof T, string>>;
1230
- });
1231
- }
1232
- },
1233
- [values, validate]
1234
- );
1235
-
1236
- const handleSubmit = useCallback(
1237
- async (e: React.FormEvent) => {
1238
- e.preventDefault();
1239
-
1240
- const validationErrors = validate(values);
1241
- setErrors(validationErrors);
1242
- setTouched(
1243
- Object.keys(values).reduce((acc, key) => ({ ...acc, [key]: true }), {})
1244
- );
1245
-
1246
- if (Object.keys(validationErrors).length > 0) return;
1247
-
1248
- setSubmitting(true);
1249
- try {
1250
- await onSubmit(values);
1251
- } finally {
1252
- setSubmitting(false);
1253
- }
1254
- },
1255
- [values, validate, onSubmit]
1256
- );
1257
-
1258
- const reset = useCallback(() => {
1259
- setValues(initialValues);
1260
- setErrors({});
1261
- setTouched({});
1262
- }, [initialValues]);
1263
-
1264
- const getFieldProps = useCallback(
1265
- (field: keyof T) => ({
1266
- value: values[field],
1267
- onChange: handleChange(field),
1268
- onBlur: handleBlur(field),
1269
- error: touched[field] ? errors[field] : undefined,
1270
- }),
1271
- [values, errors, touched, handleChange, handleBlur]
1272
- );
1273
-
1274
- return {
1275
- values,
1276
- errors,
1277
- touched,
1278
- submitting,
1279
- handleChange,
1280
- handleBlur,
1281
- handleSubmit,
1282
- reset,
1283
- getFieldProps,
1284
- setValues,
1285
- setFieldValue: (field: keyof T, value: T[keyof T]) =>
1286
- setValues((prev) => ({ ...prev, [field]: value })),
1287
- };
1288
- }
1289
121
  ```
1290
122
 
1291
123
  ## Best Practices
1292
124
 
1293
- ### Do's
1294
-
1295
- - Use compound components for complex UI with shared state
1296
- - Create custom hooks to encapsulate reusable logic
1297
- - Implement state machines for complex state transitions
1298
- - Use TypeScript for type-safe component APIs
1299
- - Prefer composition over inheritance
1300
- - Keep components focused with single responsibility
1301
- - Colocate related code and styles
1302
- - Use forwardRef for component library primitives
1303
- - Implement proper error boundaries
1304
- - Use render props for maximum flexibility
1305
-
1306
- ### Don'ts
1307
-
1308
- - Don't overuse HOCs (prefer hooks)
1309
- - Don't mutate state directly
1310
- - Don't create deeply nested component hierarchies
1311
- - Don't pass too many props (use context or composition)
1312
- - Don't ignore TypeScript errors
1313
- - Don't create components with side effects in render
1314
- - Don't forget to memoize expensive computations
1315
- - Don't skip accessibility in component design
1316
- - Don't use prop drilling for deeply nested data
1317
- - Don't forget cleanup in useEffect
1318
-
1319
- ## References
1320
-
1321
- - [React Patterns](https://reactpatterns.com/)
1322
- - [Patterns.dev](https://www.patterns.dev/)
1323
- - [Kent C. Dodds - Advanced React Patterns](https://kentcdodds.com/blog/advanced-react-patterns)
1324
- - [XState Documentation](https://xstate.js.org/docs/)
1325
- - [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/)
125
+ | Do | Avoid |
126
+ |----|-------|
127
+ | Use compound components for complex UI with shared state | Overusing HOCs (prefer hooks) |
128
+ | Create custom hooks to encapsulate reusable logic | Mutating state directly |
129
+ | Implement state machines for complex state transitions | Deeply nested component hierarchies |
130
+ | Use TypeScript for type-safe component APIs | Passing too many props (use context/composition) |
131
+ | Use forwardRef for component library primitives | Creating components with side effects in render |
132
+ | Keep components focused with single responsibility | Prop drilling for deeply nested data |