@caddis/cli 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +150 -1
- package/bundles/antigravity-plugin/agents/code-reviewer.md +47 -0
- package/bundles/antigravity-plugin/agents/preflight.md +53 -0
- package/bundles/antigravity-plugin/guard_agy.py +338 -0
- package/bundles/antigravity-plugin/hooks.json +36 -0
- package/bundles/antigravity-plugin/mcp_config.json +8 -0
- package/bundles/antigravity-plugin/mcp_ping_server.py +55 -0
- package/bundles/antigravity-plugin/plugin.json +5 -0
- package/bundles/antigravity-plugin/session_end_agy.py +57 -0
- package/bundles/antigravity-plugin/skills/_registry.md +115 -0
- package/bundles/antigravity-plugin/skills/add-rules/SKILL.md +45 -0
- package/bundles/antigravity-plugin/skills/api-design/SKILL.md +249 -0
- package/bundles/antigravity-plugin/skills/backend-development/SKILL.md +305 -0
- package/bundles/antigravity-plugin/skills/best-practices/SKILL.md +500 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/best-practices-referencer.md +263 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/codebase-context-builder.md +326 -0
- package/bundles/antigravity-plugin/skills/best-practices/agents/task-intent-analyzer.md +245 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/anti-patterns.md +571 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/before-after-examples.md +1114 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/best-practices-guide.md +513 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/common-workflows.md +692 -0
- package/bundles/antigravity-plugin/skills/best-practices/references/prompt-patterns.md +547 -0
- package/bundles/antigravity-plugin/skills/brainstorming/SKILL.md +57 -0
- package/bundles/antigravity-plugin/skills/ci-cd-pipeline/SKILL.md +315 -0
- package/bundles/antigravity-plugin/skills/code-documentation/SKILL.md +271 -0
- package/bundles/antigravity-plugin/skills/code-review/SKILL.md +122 -0
- package/bundles/antigravity-plugin/skills/codebase-audit/SKILL.md +204 -0
- package/bundles/antigravity-plugin/skills/context-curator/SKILL.md +157 -0
- package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +40 -0
- package/bundles/antigravity-plugin/skills/css-architecture/SKILL.md +305 -0
- package/bundles/antigravity-plugin/skills/css-architecture/references/RESPONSIVE-DESIGN.md +604 -0
- package/bundles/antigravity-plugin/skills/database-design/SKILL.md +177 -0
- package/bundles/antigravity-plugin/skills/db-diagram/SKILL.md +148 -0
- package/bundles/antigravity-plugin/skills/db-diagram/scripts/sql_to_graph.py +1212 -0
- package/bundles/antigravity-plugin/skills/digress/SKILL.md +61 -0
- package/bundles/antigravity-plugin/skills/draw-io/SKILL.md +162 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/aws-icons.md +677 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/layout-guidelines.md +142 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/troubleshooting.md +118 -0
- package/bundles/antigravity-plugin/skills/draw-io/references/workflows.md +103 -0
- package/bundles/antigravity-plugin/skills/draw-io/scripts/convert-drawio-to-png.sh +25 -0
- package/bundles/antigravity-plugin/skills/draw-io/scripts/find_aws_icon.py +79 -0
- package/bundles/antigravity-plugin/skills/error-handling/SKILL.md +260 -0
- package/bundles/antigravity-plugin/skills/excalidraw-db/SKILL.md +38 -0
- package/bundles/antigravity-plugin/skills/fastapi-dev/SKILL.md +300 -0
- package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +198 -0
- package/bundles/antigravity-plugin/skills/frontend-design/SKILL.md +193 -0
- package/bundles/antigravity-plugin/skills/frontend-design/references/my-tech-stack.md +127 -0
- package/bundles/antigravity-plugin/skills/gh-cli/SKILL.md +195 -0
- package/bundles/antigravity-plugin/skills/git-commit/SKILL.md +285 -0
- package/bundles/antigravity-plugin/skills/golden-plan/SKILL.md +577 -0
- package/bundles/antigravity-plugin/skills/handoff/SKILL.md +100 -0
- package/bundles/antigravity-plugin/skills/implement/SKILL.md +114 -0
- package/bundles/antigravity-plugin/skills/javascript-typescript/SKILL.md +142 -0
- package/bundles/antigravity-plugin/skills/kb/SKILL.md +60 -0
- package/bundles/antigravity-plugin/skills/mermaid-db/SKILL.md +33 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/SKILL.md +236 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/ENHANCEMENTS.md +264 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/MERMAID-SUMMARY.md +137 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/advanced-features.md +556 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/architecture-diagrams.md +192 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/c4-diagrams.md +410 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/class-diagrams.md +361 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/erd-diagrams.md +510 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/flowcharts.md +450 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/sequence-diagrams.md +394 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/troubleshooting.md +335 -0
- package/bundles/antigravity-plugin/skills/mermaid-diagrams/references/workflows.md +418 -0
- package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +68 -0
- package/bundles/antigravity-plugin/skills/mockup/SKILL.md +242 -0
- package/bundles/antigravity-plugin/skills/particle-art/SKILL.md +243 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/canvas-utils.ts +171 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/dot-field.template.tsx +203 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/flow-field.template.tsx +263 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/node-shape.template.tsx +261 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/shape-sampler.ts +281 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/stipple-morph.template.tsx +167 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/stipple.template.tsx +175 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/trail-ghost.template.tsx +266 -0
- package/bundles/antigravity-plugin/skills/particle-art/references/usage-examples.md +320 -0
- package/bundles/antigravity-plugin/skills/playwright/API_REFERENCE.md +653 -0
- package/bundles/antigravity-plugin/skills/playwright/SKILL.md +454 -0
- package/bundles/antigravity-plugin/skills/playwright/lib/helpers.js +441 -0
- package/bundles/antigravity-plugin/skills/playwright/package.json +26 -0
- package/bundles/antigravity-plugin/skills/playwright/run.js +228 -0
- package/bundles/antigravity-plugin/skills/prd/SKILL.md +107 -0
- package/bundles/antigravity-plugin/skills/preflight/SKILL.md +435 -0
- package/bundles/antigravity-plugin/skills/python/SKILL.md +388 -0
- package/bundles/antigravity-plugin/skills/react-best-practices/SKILL.md +269 -0
- package/bundles/antigravity-plugin/skills/react-dev/README.md +404 -0
- package/bundles/antigravity-plugin/skills/react-dev/SKILL.md +459 -0
- package/bundles/antigravity-plugin/skills/react-dev/examples/generic-components.md +579 -0
- package/bundles/antigravity-plugin/skills/react-dev/examples/server-components.md +579 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/event-handlers.md +574 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/hooks.md +456 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/react-19-patterns.md +638 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/react-router.md +1002 -0
- package/bundles/antigravity-plugin/skills/react-dev/references/tanstack-router.md +587 -0
- package/bundles/antigravity-plugin/skills/refactoring/SKILL.md +486 -0
- package/bundles/antigravity-plugin/skills/resume/SKILL.md +36 -0
- package/bundles/antigravity-plugin/skills/security-review/SKILL.md +196 -0
- package/bundles/antigravity-plugin/skills/setup-project-ai/SKILL.md +61 -0
- package/bundles/antigravity-plugin/skills/ship/SKILL.md +107 -0
- package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +103 -0
- package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +102 -0
- package/bundles/antigravity-plugin/skills/skill-creator/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin/skills/skill-creator/SKILL.md +491 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/analyzer.md +274 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/comparator.md +202 -0
- package/bundles/antigravity-plugin/skills/skill-creator/agents/grader.md +223 -0
- package/bundles/antigravity-plugin/skills/skill-creator/assets/eval_review.html +146 -0
- package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/bundles/antigravity-plugin/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/bundles/antigravity-plugin/skills/skill-creator/references/schemas.md +430 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/__init__.py +0 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/generate_report.py +326 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/improve_description.py +247 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/package_skill.py +136 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_eval.py +310 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/run_loop.py +328 -0
- package/bundles/antigravity-plugin/skills/skill-creator/scripts/utils.py +47 -0
- package/bundles/antigravity-plugin/skills/sql/SKILL.md +321 -0
- package/bundles/antigravity-plugin/skills/tdd/SKILL.md +37 -0
- package/bundles/antigravity-plugin/skills/tdd-workflow/SKILL.md +188 -0
- package/bundles/antigravity-plugin/skills/technical-writing/SKILL.md +286 -0
- package/bundles/antigravity-plugin/skills/test-strategy/SKILL.md +155 -0
- package/bundles/antigravity-plugin/skills/ui-brief/SKILL.md +84 -0
- package/bundles/antigravity-plugin/skills/ui-review/SKILL.md +176 -0
- package/bundles/antigravity-plugin/skills/ui-review/references/framework-fixes.md +471 -0
- package/bundles/antigravity-plugin/skills/ui-review/references/visual-checklist.md +236 -0
- package/bundles/antigravity-plugin/skills/usage-review/SKILL.md +77 -0
- package/bundles/antigravity-plugin/skills/use-model/SKILL.md +64 -0
- package/bundles/antigravity-plugin/skills/using-git-worktrees/SKILL.md +217 -0
- package/bundles/antigravity-plugin/skills/version/SKILL.md +18 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/DESIGN_TOKENS.md +487 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/IMPLEMENTATION_GUIDE.md +177 -0
- package/bundles/antigravity-plugin/skills/warm-editorial-ui/SKILL.md +732 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/LICENSE.txt +202 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/SKILL.md +97 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/console_logging.py +35 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/bundles/antigravity-plugin/skills/webapp-testing/scripts/with_server.py +106 -0
- package/bundles/antigravity-plugin/skills/windows-deployment/SKILL.md +880 -0
- package/bundles/antigravity-plugin/skills/writing-plans/SKILL.md +384 -0
- package/bundles/antigravity-plugin/statusline-command-agy.sh +91 -0
- package/bundles/antigravity-plugin/warm_start_agy.py +149 -0
- package/bundles/manifest.json +6 -0
- package/dist/cli.js +5363 -0
- package/package.json +61 -4
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# Hook TypeScript Patterns
|
|
2
|
+
|
|
3
|
+
Type-safe hook patterns for useState, useRef, useReducer, useContext, and custom hooks.
|
|
4
|
+
|
|
5
|
+
## useState
|
|
6
|
+
|
|
7
|
+
Type inference works for simple types; explicit typing needed for unions/null.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// Inference works
|
|
11
|
+
const [count, setCount] = useState(0); // number
|
|
12
|
+
const [name, setName] = useState(''); // string
|
|
13
|
+
const [items, setItems] = useState<string[]>([]); // explicit for empty arrays
|
|
14
|
+
|
|
15
|
+
// Explicit for unions/null
|
|
16
|
+
const [user, setUser] = useState<User | null>(null);
|
|
17
|
+
const [status, setStatus] = useState<'idle' | 'loading' | 'success'>('idle');
|
|
18
|
+
|
|
19
|
+
// Complex initial state
|
|
20
|
+
type FormData = { name: string; email: string };
|
|
21
|
+
const [formData, setFormData] = useState<FormData>({
|
|
22
|
+
name: '',
|
|
23
|
+
email: '',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Lazy initialization
|
|
27
|
+
const [data, setData] = useState<Data>(() => {
|
|
28
|
+
const cached = localStorage.getItem('data');
|
|
29
|
+
return cached ? JSON.parse(cached) : defaultData;
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## useRef
|
|
34
|
+
|
|
35
|
+
Distinguish DOM refs (null initial) from mutable value refs (value initial).
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// DOM element ref - null initial, readonly .current
|
|
39
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
40
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
41
|
+
const divRef = useRef<HTMLDivElement>(null);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
inputRef.current?.focus(); // Optional chaining for null
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
// Mutable value ref - non-null initial, mutable .current
|
|
48
|
+
const countRef = useRef<number>(0);
|
|
49
|
+
countRef.current += 1; // No optional chaining
|
|
50
|
+
|
|
51
|
+
const previousValueRef = useRef<string | undefined>(undefined);
|
|
52
|
+
previousValueRef.current = currentValue;
|
|
53
|
+
|
|
54
|
+
// Interval/timeout ref
|
|
55
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
56
|
+
const intervalRef = useRef<ReturnType<typeof setInterval>>();
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
timeoutRef.current = setTimeout(() => {}, 1000);
|
|
60
|
+
return () => {
|
|
61
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
62
|
+
};
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
// Callback ref for dynamic elements
|
|
66
|
+
const callbackRef = useCallback((node: HTMLDivElement | null) => {
|
|
67
|
+
if (node) {
|
|
68
|
+
node.scrollIntoView({ behavior: 'smooth' });
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## useReducer
|
|
74
|
+
|
|
75
|
+
Typed actions with discriminated unions.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
type State = {
|
|
79
|
+
count: number;
|
|
80
|
+
status: 'idle' | 'loading' | 'success' | 'error';
|
|
81
|
+
error?: string;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type Action =
|
|
85
|
+
| { type: 'increment' }
|
|
86
|
+
| { type: 'decrement' }
|
|
87
|
+
| { type: 'set'; payload: number }
|
|
88
|
+
| { type: 'setStatus'; payload: State['status'] }
|
|
89
|
+
| { type: 'setError'; payload: string };
|
|
90
|
+
|
|
91
|
+
function reducer(state: State, action: Action): State {
|
|
92
|
+
switch (action.type) {
|
|
93
|
+
case 'increment':
|
|
94
|
+
return { ...state, count: state.count + 1 };
|
|
95
|
+
case 'decrement':
|
|
96
|
+
return { ...state, count: state.count - 1 };
|
|
97
|
+
case 'set':
|
|
98
|
+
return { ...state, count: action.payload };
|
|
99
|
+
case 'setStatus':
|
|
100
|
+
return { ...state, status: action.payload };
|
|
101
|
+
case 'setError':
|
|
102
|
+
return { ...state, status: 'error', error: action.payload };
|
|
103
|
+
default:
|
|
104
|
+
return state;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function Component() {
|
|
109
|
+
const [state, dispatch] = useReducer(reducer, {
|
|
110
|
+
count: 0,
|
|
111
|
+
status: 'idle',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
dispatch({ type: 'set', payload: 10 }); // Type-safe
|
|
115
|
+
dispatch({ type: 'set' }); // Error: payload required
|
|
116
|
+
dispatch({ type: 'unknown' }); // Error: invalid action type
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Reducer with Context
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
type AuthState = {
|
|
124
|
+
user: User | null;
|
|
125
|
+
isAuthenticated: boolean;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
type AuthAction =
|
|
129
|
+
| { type: 'login'; payload: User }
|
|
130
|
+
| { type: 'logout' };
|
|
131
|
+
|
|
132
|
+
type AuthContextValue = {
|
|
133
|
+
state: AuthState;
|
|
134
|
+
dispatch: React.Dispatch<AuthAction>;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
138
|
+
|
|
139
|
+
function authReducer(state: AuthState, action: AuthAction): AuthState {
|
|
140
|
+
switch (action.type) {
|
|
141
|
+
case 'login':
|
|
142
|
+
return { user: action.payload, isAuthenticated: true };
|
|
143
|
+
case 'logout':
|
|
144
|
+
return { user: null, isAuthenticated: false };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
149
|
+
const [state, dispatch] = useReducer(authReducer, {
|
|
150
|
+
user: null,
|
|
151
|
+
isAuthenticated: false,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<AuthContext.Provider value={{ state, dispatch }}>
|
|
156
|
+
{children}
|
|
157
|
+
</AuthContext.Provider>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function useAuth() {
|
|
162
|
+
const context = useContext(AuthContext);
|
|
163
|
+
if (!context) throw new Error('useAuth must be used within AuthProvider');
|
|
164
|
+
return context;
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## useContext
|
|
169
|
+
|
|
170
|
+
Typed context with and without default values.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// Context with default value
|
|
174
|
+
type Theme = 'light' | 'dark';
|
|
175
|
+
const ThemeContext = createContext<Theme>('light');
|
|
176
|
+
|
|
177
|
+
function useTheme() {
|
|
178
|
+
return useContext(ThemeContext); // Always Theme, never null
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Context without default (must handle null)
|
|
182
|
+
type User = { id: string; name: string };
|
|
183
|
+
const UserContext = createContext<User | null>(null);
|
|
184
|
+
|
|
185
|
+
function useUser() {
|
|
186
|
+
const user = useContext(UserContext);
|
|
187
|
+
if (!user) throw new Error('useUser must be used within UserProvider');
|
|
188
|
+
return user; // Type narrowed to User
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Context with complex value
|
|
192
|
+
type AppContextValue = {
|
|
193
|
+
theme: Theme;
|
|
194
|
+
user: User | null;
|
|
195
|
+
setTheme: (theme: Theme) => void;
|
|
196
|
+
login: (user: User) => void;
|
|
197
|
+
logout: () => void;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const AppContext = createContext<AppContextValue | null>(null);
|
|
201
|
+
|
|
202
|
+
function useApp() {
|
|
203
|
+
const context = useContext(AppContext);
|
|
204
|
+
if (!context) throw new Error('useApp must be used within AppProvider');
|
|
205
|
+
return context;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Custom Hooks
|
|
210
|
+
|
|
211
|
+
Return type patterns for simple and complex hooks.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Object return - properties accessed by name
|
|
215
|
+
function useCounter(initial: number) {
|
|
216
|
+
const [count, setCount] = useState(initial);
|
|
217
|
+
const increment = () => setCount((c) => c + 1);
|
|
218
|
+
const decrement = () => setCount((c) => c - 1);
|
|
219
|
+
const reset = () => setCount(initial);
|
|
220
|
+
|
|
221
|
+
return { count, increment, decrement, reset };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Usage
|
|
225
|
+
const { count, increment } = useCounter(0);
|
|
226
|
+
|
|
227
|
+
// Tuple return - positional destructuring
|
|
228
|
+
function useToggle(initial = false): [boolean, () => void, () => void, () => void] {
|
|
229
|
+
const [value, setValue] = useState(initial);
|
|
230
|
+
const toggle = () => setValue((v) => !v);
|
|
231
|
+
const setTrue = () => setValue(true);
|
|
232
|
+
const setFalse = () => setValue(false);
|
|
233
|
+
|
|
234
|
+
return [value, toggle, setTrue, setFalse];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Usage
|
|
238
|
+
const [isOpen, toggleOpen, open, close] = useToggle();
|
|
239
|
+
|
|
240
|
+
// as const for tuple inference
|
|
241
|
+
function useLocalStorage<T>(key: string, initial: T) {
|
|
242
|
+
const [value, setValue] = useState<T>(() => {
|
|
243
|
+
const stored = localStorage.getItem(key);
|
|
244
|
+
return stored ? JSON.parse(stored) : initial;
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
249
|
+
}, [key, value]);
|
|
250
|
+
|
|
251
|
+
return [value, setValue] as const; // readonly tuple
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Generic custom hook
|
|
255
|
+
function useFetch<T>(url: string) {
|
|
256
|
+
const [data, setData] = useState<T | null>(null);
|
|
257
|
+
const [loading, setLoading] = useState(true);
|
|
258
|
+
const [error, setError] = useState<Error | null>(null);
|
|
259
|
+
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
let cancelled = false;
|
|
262
|
+
|
|
263
|
+
fetch(url)
|
|
264
|
+
.then((res) => res.json())
|
|
265
|
+
.then((json: T) => {
|
|
266
|
+
if (!cancelled) {
|
|
267
|
+
setData(json);
|
|
268
|
+
setLoading(false);
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
.catch((err) => {
|
|
272
|
+
if (!cancelled) {
|
|
273
|
+
setError(err);
|
|
274
|
+
setLoading(false);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return () => {
|
|
279
|
+
cancelled = true;
|
|
280
|
+
};
|
|
281
|
+
}, [url]);
|
|
282
|
+
|
|
283
|
+
return { data, loading, error };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Usage - T inferred from usage or explicit
|
|
287
|
+
const { data } = useFetch<User[]>('/api/users');
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## useCallback and useMemo
|
|
291
|
+
|
|
292
|
+
Typed callbacks and memoized values.
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
// useCallback with typed parameters
|
|
296
|
+
const handleClick = useCallback((id: string, event: React.MouseEvent) => {
|
|
297
|
+
console.log(id, event.target);
|
|
298
|
+
}, []);
|
|
299
|
+
|
|
300
|
+
// useCallback returning value
|
|
301
|
+
const formatDate = useCallback((date: Date): string => {
|
|
302
|
+
return date.toLocaleDateString();
|
|
303
|
+
}, []);
|
|
304
|
+
|
|
305
|
+
// useMemo with explicit return type
|
|
306
|
+
const sortedItems = useMemo((): Item[] => {
|
|
307
|
+
return [...items].sort((a, b) => a.name.localeCompare(b.name));
|
|
308
|
+
}, [items]);
|
|
309
|
+
|
|
310
|
+
// useMemo with complex computation
|
|
311
|
+
const stats = useMemo(() => {
|
|
312
|
+
return {
|
|
313
|
+
total: items.length,
|
|
314
|
+
average: items.reduce((a, b) => a + b.value, 0) / items.length,
|
|
315
|
+
max: Math.max(...items.map((i) => i.value)),
|
|
316
|
+
};
|
|
317
|
+
}, [items]);
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## useImperativeHandle
|
|
321
|
+
|
|
322
|
+
Expose imperative methods from components.
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
type InputHandle = {
|
|
326
|
+
focus: () => void;
|
|
327
|
+
clear: () => void;
|
|
328
|
+
getValue: () => string;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
type InputProps = {
|
|
332
|
+
ref?: React.Ref<InputHandle>;
|
|
333
|
+
label: string;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
function CustomInput({ ref, label }: InputProps) {
|
|
337
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
338
|
+
|
|
339
|
+
useImperativeHandle(ref, () => ({
|
|
340
|
+
focus: () => inputRef.current?.focus(),
|
|
341
|
+
clear: () => {
|
|
342
|
+
if (inputRef.current) inputRef.current.value = '';
|
|
343
|
+
},
|
|
344
|
+
getValue: () => inputRef.current?.value ?? '',
|
|
345
|
+
}));
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<div>
|
|
349
|
+
<label>{label}</label>
|
|
350
|
+
<input ref={inputRef} />
|
|
351
|
+
</div>
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Usage
|
|
356
|
+
function Form() {
|
|
357
|
+
const inputRef = useRef<InputHandle>(null);
|
|
358
|
+
|
|
359
|
+
const handleSubmit = () => {
|
|
360
|
+
const value = inputRef.current?.getValue();
|
|
361
|
+
inputRef.current?.clear();
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
return (
|
|
365
|
+
<form>
|
|
366
|
+
<CustomInput ref={inputRef} label="Name" />
|
|
367
|
+
<button onClick={() => inputRef.current?.focus()}>Focus</button>
|
|
368
|
+
</form>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## useLayoutEffect
|
|
374
|
+
|
|
375
|
+
Same signature as useEffect, runs synchronously after DOM mutations.
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
function Tooltip({ targetRef }: { targetRef: React.RefObject<HTMLElement> }) {
|
|
379
|
+
const tooltipRef = useRef<HTMLDivElement>(null);
|
|
380
|
+
const [position, setPosition] = useState({ top: 0, left: 0 });
|
|
381
|
+
|
|
382
|
+
useLayoutEffect(() => {
|
|
383
|
+
if (targetRef.current && tooltipRef.current) {
|
|
384
|
+
const rect = targetRef.current.getBoundingClientRect();
|
|
385
|
+
setPosition({
|
|
386
|
+
top: rect.bottom + 8,
|
|
387
|
+
left: rect.left,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}, [targetRef]);
|
|
391
|
+
|
|
392
|
+
return (
|
|
393
|
+
<div
|
|
394
|
+
ref={tooltipRef}
|
|
395
|
+
style={{ position: 'fixed', top: position.top, left: position.left }}
|
|
396
|
+
>
|
|
397
|
+
Tooltip content
|
|
398
|
+
</div>
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## useId
|
|
404
|
+
|
|
405
|
+
Generate unique IDs for accessibility.
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
function FormField({ label }: { label: string }) {
|
|
409
|
+
const id = useId();
|
|
410
|
+
const errorId = useId();
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
<div>
|
|
414
|
+
<label htmlFor={id}>{label}</label>
|
|
415
|
+
<input id={id} aria-describedby={errorId} />
|
|
416
|
+
<span id={errorId}>Error message</span>
|
|
417
|
+
</div>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## useSyncExternalStore
|
|
423
|
+
|
|
424
|
+
Subscribe to external stores with SSR support.
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
type Store<T> = {
|
|
428
|
+
getState: () => T;
|
|
429
|
+
subscribe: (callback: () => void) => () => void;
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
function useStore<T>(store: Store<T>): T {
|
|
433
|
+
return useSyncExternalStore(
|
|
434
|
+
store.subscribe,
|
|
435
|
+
store.getState,
|
|
436
|
+
store.getState // Server snapshot
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Example: window width store
|
|
441
|
+
const widthStore: Store<number> = {
|
|
442
|
+
getState: () => (typeof window !== 'undefined' ? window.innerWidth : 0),
|
|
443
|
+
subscribe: (callback) => {
|
|
444
|
+
window.addEventListener('resize', callback);
|
|
445
|
+
return () => window.removeEventListener('resize', callback);
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
function useWindowWidth() {
|
|
450
|
+
return useSyncExternalStore(
|
|
451
|
+
widthStore.subscribe,
|
|
452
|
+
widthStore.getState,
|
|
453
|
+
() => 0 // Server fallback
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
```
|