@the-bearded-bear/claude-craft 8.8.1 → 8.8.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/Dev/i18n/de/Angular/commands/check-compliance.md +218 -168
- package/Dev/i18n/de/Common/agents/refactoring-specialist.md +146 -69
- package/Dev/i18n/de/Common/commands/add-technology.md +166 -68
- package/Dev/i18n/de/Common/commands/setup-ci.md +216 -33
- package/Dev/i18n/de/Laravel/commands/check-compliance.md +223 -115
- package/Dev/i18n/de/Python/checklists/new-feature.md +319 -44
- package/Dev/i18n/de/React/rules/02-architecture.md +483 -322
- package/Dev/i18n/de/React/rules/03-coding-standards.md +584 -368
- package/Dev/i18n/de/Symfony/commands/check-compliance.md +186 -114
- package/Dev/i18n/de/Symfony/rules/07-testing-symfony.md +679 -10
- package/Dev/i18n/de/UIUX/commands/a11y-component.md +284 -26
- package/Dev/i18n/de/UIUX/commands/design-tokens.md +199 -35
- package/Dev/i18n/de/UIUX/commands/user-flow.md +179 -16
- package/Dev/i18n/de/VueJS/commands/check-compliance.md +227 -102
- package/Dev/i18n/es/Angular/commands/check-compliance.md +217 -167
- package/Dev/i18n/es/Common/commands/add-technology.md +158 -60
- package/Dev/i18n/es/Laravel/commands/check-compliance.md +223 -115
- package/Dev/i18n/es/React/commands/check-testing.md +263 -169
- package/Dev/i18n/es/ReactNative/commands/check-testing.md +256 -198
- package/Dev/i18n/es/Symfony/commands/check-compliance.md +189 -117
- package/Dev/i18n/es/Symfony/commands/check-testing.md +301 -102
- package/Dev/i18n/es/Symfony/commands/migration-plan.md +267 -34
- package/Dev/i18n/es/UIUX/commands/a11y-component.md +274 -16
- package/Dev/i18n/es/UIUX/commands/design-tokens.md +186 -22
- package/Dev/i18n/es/UIUX/commands/user-flow.md +173 -10
- package/Dev/i18n/es/VueJS/commands/check-compliance.md +227 -102
- package/Dev/i18n/fr/Angular/commands/check-compliance.md +217 -167
- package/Dev/i18n/fr/Common/commands/ralph-run.md +138 -60
- package/Dev/i18n/fr/Laravel/commands/check-compliance.md +223 -115
- package/Dev/i18n/fr/React/commands/check-architecture.md +370 -53
- package/Dev/i18n/fr/React/commands/check-code-quality.md +454 -64
- package/Dev/i18n/fr/React/commands/check-testing.md +476 -102
- package/Dev/i18n/fr/Symfony/commands/check-compliance.md +188 -116
- package/Dev/i18n/fr/VueJS/commands/check-compliance.md +227 -102
- package/Dev/i18n/pt/Angular/commands/check-compliance.md +225 -175
- package/Dev/i18n/pt/Common/agents/ralph-conductor.md +211 -30
- package/Dev/i18n/pt/Common/commands/add-technology.md +149 -51
- package/Dev/i18n/pt/Common/commands/ralph-run.md +239 -35
- package/Dev/i18n/pt/Flutter/commands/analyze-performance.md +331 -46
- package/Dev/i18n/pt/Flutter/commands/check-compliance.md +234 -29
- package/Dev/i18n/pt/Flutter/commands/generate-feature.md +526 -51
- package/Dev/i18n/pt/Flutter/commands/generate-widget.md +515 -27
- package/Dev/i18n/pt/Flutter/commands/golden-update.md +322 -46
- package/Dev/i18n/pt/Flutter/commands/localization-check.md +278 -37
- package/Dev/i18n/pt/Laravel/commands/check-compliance.md +212 -104
- package/Dev/i18n/pt/React/commands/accessibility-check.md +151 -42
- package/Dev/i18n/pt/React/commands/bundle-analyze.md +300 -25
- package/Dev/i18n/pt/React/commands/check-architecture.md +369 -34
- package/Dev/i18n/pt/React/commands/check-code-quality.md +440 -34
- package/Dev/i18n/pt/React/commands/check-compliance.md +232 -62
- package/Dev/i18n/pt/React/commands/check-security.md +370 -31
- package/Dev/i18n/pt/React/commands/check-testing.md +473 -43
- package/Dev/i18n/pt/React/commands/generate-component.md +454 -19
- package/Dev/i18n/pt/React/commands/generate-hook.md +425 -24
- package/Dev/i18n/pt/React/commands/storybook-story.md +520 -35
- package/Dev/i18n/pt/ReactNative/commands/app-size.md +358 -387
- package/Dev/i18n/pt/ReactNative/commands/check-code-quality.md +246 -75
- package/Dev/i18n/pt/ReactNative/commands/check-compliance.md +191 -85
- package/Dev/i18n/pt/ReactNative/commands/check-security.md +363 -115
- package/Dev/i18n/pt/ReactNative/commands/check-testing.md +269 -79
- package/Dev/i18n/pt/ReactNative/commands/generate-screen.md +491 -324
- package/Dev/i18n/pt/ReactNative/commands/native-module.md +539 -82
- package/Dev/i18n/pt/ReactNative/commands/store-prepare.md +429 -185
- package/Dev/i18n/pt/ReactNative/rules/02-architecture.md +1084 -31
- package/Dev/i18n/pt/Symfony/commands/check-compliance.md +195 -123
- package/Dev/i18n/pt/UIUX/commands/a11y-audit.md +208 -24
- package/Dev/i18n/pt/UIUX/commands/a11y-component.md +281 -23
- package/Dev/i18n/pt/UIUX/commands/design-tokens.md +197 -33
- package/Dev/i18n/pt/UIUX/commands/user-flow.md +183 -20
- package/Dev/i18n/pt/VueJS/commands/check-compliance.md +228 -103
- package/Dev/i18n/pt/Workflow/commands/analyze.md +147 -146
- package/Dev/i18n/pt/Workflow/commands/design.md +205 -204
- package/Dev/i18n/pt/Workflow/commands/implement.md +184 -184
- package/Project/i18n/de/Sprint/commands/dev.md +339 -86
- package/Project/i18n/es/Sprint/commands/dev.md +342 -91
- package/Project/i18n/pt/Sprint/commands/dev.md +343 -92
- package/bundles/cursor/.cursorrules +1 -1
- package/bundles/windsurf/.windsurfrules +1 -1
- package/package.json +1 -1
|
@@ -4,21 +4,21 @@ description: Gerar Hook Personalizado
|
|
|
4
4
|
|
|
5
5
|
# Gerar Hook Personalizado
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Gere um novo hook React personalizado com TypeScript e testes.
|
|
8
8
|
|
|
9
9
|
## O Que Este Comando Faz
|
|
10
10
|
|
|
11
|
-
1. **
|
|
12
|
-
- Criar arquivo
|
|
11
|
+
1. **Geração de Hook**
|
|
12
|
+
- Criar arquivo do hook
|
|
13
13
|
- Gerar tipos TypeScript
|
|
14
14
|
- Criar arquivo de teste
|
|
15
|
-
- Adicionar exemplos de uso
|
|
15
|
+
- Adicionar exemplos de uso nos comentários
|
|
16
16
|
|
|
17
|
-
2. **Templates
|
|
18
|
-
-
|
|
19
|
-
-
|
|
17
|
+
2. **Templates Utilizados**
|
|
18
|
+
- Função de hook personalizado
|
|
19
|
+
- Definições de tipos
|
|
20
20
|
- Estrutura de teste com renderHook
|
|
21
|
-
-
|
|
21
|
+
- Documentação JSDoc
|
|
22
22
|
|
|
23
23
|
3. **Arquivos Gerados**
|
|
24
24
|
```
|
|
@@ -36,15 +36,17 @@ npm run generate:hook useHookName
|
|
|
36
36
|
# Com caminho personalizado
|
|
37
37
|
npm run generate:hook features/users/hooks/useUserData
|
|
38
38
|
|
|
39
|
-
# Hook
|
|
39
|
+
# Hook específico de feature
|
|
40
40
|
npm run generate:hook features/auth/hooks/useLogin
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
## Modo
|
|
43
|
+
## Modo de Planejamento
|
|
44
44
|
|
|
45
|
-
> **O modo
|
|
45
|
+
> **O modo de planejamento é obrigatório.** Antes de executar, o Claude ativa o modo de planejamento para analisar o código impactado, propor um plano de implementação e aguardar sua validação antes de realizar qualquer alteração.
|
|
46
46
|
|
|
47
|
-
##
|
|
47
|
+
## Templates de Hook
|
|
48
|
+
|
|
49
|
+
### 1. Hook de Estado Simples
|
|
48
50
|
|
|
49
51
|
```typescript
|
|
50
52
|
// useCounter.ts
|
|
@@ -67,8 +69,8 @@ export interface UseCounterReturn {
|
|
|
67
69
|
/**
|
|
68
70
|
* Hook para gerenciar estado de contador
|
|
69
71
|
*
|
|
70
|
-
* @param options -
|
|
71
|
-
* @returns Estado e
|
|
72
|
+
* @param options - Opções de configuração
|
|
73
|
+
* @returns Estado e métodos do contador
|
|
72
74
|
*
|
|
73
75
|
* @example
|
|
74
76
|
* const { count, increment, decrement } = useCounter({ initialValue: 0 });
|
|
@@ -107,6 +109,251 @@ export const useCounter = (
|
|
|
107
109
|
};
|
|
108
110
|
```
|
|
109
111
|
|
|
112
|
+
### 2. Hook de Busca de Dados
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// useUser.ts
|
|
116
|
+
import { useQuery } from '@tanstack/react-query';
|
|
117
|
+
import { userService } from '@/services/user.service';
|
|
118
|
+
import type { User } from '@/types/user.types';
|
|
119
|
+
|
|
120
|
+
export interface UseUserOptions {
|
|
121
|
+
enabled?: boolean;
|
|
122
|
+
refetchInterval?: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface UseUserReturn {
|
|
126
|
+
user: User | undefined;
|
|
127
|
+
isLoading: boolean;
|
|
128
|
+
error: Error | null;
|
|
129
|
+
refetch: () => void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Hook para buscar dados de um usuário
|
|
134
|
+
*
|
|
135
|
+
* @param userId - O ID do usuário a buscar
|
|
136
|
+
* @param options - Opções de query
|
|
137
|
+
* @returns Dados do usuário e estado da query
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* const { user, isLoading } = useUser('123');
|
|
141
|
+
*/
|
|
142
|
+
export const useUser = (
|
|
143
|
+
userId: string,
|
|
144
|
+
options: UseUserOptions = {}
|
|
145
|
+
): UseUserReturn => {
|
|
146
|
+
const { data, isLoading, error, refetch } = useQuery({
|
|
147
|
+
queryKey: ['user', userId],
|
|
148
|
+
queryFn: () => userService.getById(userId),
|
|
149
|
+
enabled: options.enabled !== false,
|
|
150
|
+
refetchInterval: options.refetchInterval
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
user: data,
|
|
155
|
+
isLoading,
|
|
156
|
+
error: error as Error | null,
|
|
157
|
+
refetch
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 3. Hook de Armazenamento Local
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// useLocalStorage.ts
|
|
166
|
+
import { useState, useEffect } from 'react';
|
|
167
|
+
|
|
168
|
+
export interface UseLocalStorageOptions<T> {
|
|
169
|
+
serializer?: (value: T) => string;
|
|
170
|
+
deserializer?: (value: string) => T;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Hook para sincronizar estado com o localStorage
|
|
175
|
+
*
|
|
176
|
+
* @param key - A chave do localStorage
|
|
177
|
+
* @param initialValue - Valor inicial caso a chave não exista
|
|
178
|
+
* @param options - Opções de serialização
|
|
179
|
+
* @returns Valor do estado e setter
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* const [theme, setTheme] = useLocalStorage('theme', 'light');
|
|
183
|
+
*/
|
|
184
|
+
export function useLocalStorage<T>(
|
|
185
|
+
key: string,
|
|
186
|
+
initialValue: T,
|
|
187
|
+
options: UseLocalStorageOptions<T> = {}
|
|
188
|
+
): [T, (value: T | ((val: T) => T)) => void] {
|
|
189
|
+
const {
|
|
190
|
+
serializer = JSON.stringify,
|
|
191
|
+
deserializer = JSON.parse
|
|
192
|
+
} = options;
|
|
193
|
+
|
|
194
|
+
// Obter o valor inicial do localStorage ou usar initialValue
|
|
195
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
196
|
+
try {
|
|
197
|
+
const item = window.localStorage.getItem(key);
|
|
198
|
+
return item ? deserializer(item) : initialValue;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error(`Error reading localStorage key "${key}":`, error);
|
|
201
|
+
return initialValue;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Atualizar localStorage quando o valor mudar
|
|
206
|
+
const setValue = (value: T | ((val: T) => T)) => {
|
|
207
|
+
try {
|
|
208
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
209
|
+
setStoredValue(valueToStore);
|
|
210
|
+
window.localStorage.setItem(key, serializer(valueToStore));
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error(`Error setting localStorage key "${key}":`, error);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return [storedValue, setValue];
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 4. Hook de Formulário
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// useForm.ts
|
|
224
|
+
import { useState, useCallback, FormEvent } from 'react';
|
|
225
|
+
|
|
226
|
+
export interface UseFormOptions<T> {
|
|
227
|
+
initialValues: T;
|
|
228
|
+
onSubmit: (values: T) => void | Promise<void>;
|
|
229
|
+
validate?: (values: T) => Partial<Record<keyof T, string>>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface UseFormReturn<T> {
|
|
233
|
+
values: T;
|
|
234
|
+
errors: Partial<Record<keyof T, string>>;
|
|
235
|
+
isSubmitting: boolean;
|
|
236
|
+
handleChange: (name: keyof T, value: unknown) => void;
|
|
237
|
+
handleSubmit: (e: FormEvent) => Promise<void>;
|
|
238
|
+
reset: () => void;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Hook para gerenciar estado e validação de formulário
|
|
243
|
+
*
|
|
244
|
+
* @param options - Configuração do formulário
|
|
245
|
+
* @returns Estado e handlers do formulário
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* const form = useForm({
|
|
249
|
+
* initialValues: { email: '', password: '' },
|
|
250
|
+
* onSubmit: async (values) => { ... },
|
|
251
|
+
* validate: (values) => { ... }
|
|
252
|
+
* });
|
|
253
|
+
*/
|
|
254
|
+
export function useForm<T extends Record<string, unknown>>(
|
|
255
|
+
options: UseFormOptions<T>
|
|
256
|
+
): UseFormReturn<T> {
|
|
257
|
+
const { initialValues, onSubmit, validate } = options;
|
|
258
|
+
|
|
259
|
+
const [values, setValues] = useState<T>(initialValues);
|
|
260
|
+
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
|
|
261
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
262
|
+
|
|
263
|
+
const handleChange = useCallback((name: keyof T, value: unknown) => {
|
|
264
|
+
setValues((prev) => ({ ...prev, [name]: value }));
|
|
265
|
+
// Limpar erro deste campo
|
|
266
|
+
setErrors((prev) => ({ ...prev, [name]: undefined }));
|
|
267
|
+
}, []);
|
|
268
|
+
|
|
269
|
+
const handleSubmit = useCallback(
|
|
270
|
+
async (e: FormEvent) => {
|
|
271
|
+
e.preventDefault();
|
|
272
|
+
setIsSubmitting(true);
|
|
273
|
+
|
|
274
|
+
// Validar
|
|
275
|
+
if (validate) {
|
|
276
|
+
const validationErrors = validate(values);
|
|
277
|
+
if (Object.keys(validationErrors).length > 0) {
|
|
278
|
+
setErrors(validationErrors);
|
|
279
|
+
setIsSubmitting(false);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
await onSubmit(values);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error('Form submission error:', error);
|
|
288
|
+
} finally {
|
|
289
|
+
setIsSubmitting(false);
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
[values, validate, onSubmit]
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const reset = useCallback(() => {
|
|
296
|
+
setValues(initialValues);
|
|
297
|
+
setErrors({});
|
|
298
|
+
setIsSubmitting(false);
|
|
299
|
+
}, [initialValues]);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
values,
|
|
303
|
+
errors,
|
|
304
|
+
isSubmitting,
|
|
305
|
+
handleChange,
|
|
306
|
+
handleSubmit,
|
|
307
|
+
reset
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 5. Hook de Media Query
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// useMediaQuery.ts
|
|
316
|
+
import { useState, useEffect } from 'react';
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Hook para monitorar correspondência de media query
|
|
320
|
+
*
|
|
321
|
+
* @param query - A string de media query
|
|
322
|
+
* @returns Se a media query corresponde ou não
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* const isMobile = useMediaQuery('(max-width: 768px)');
|
|
326
|
+
*/
|
|
327
|
+
export const useMediaQuery = (query: string): boolean => {
|
|
328
|
+
const [matches, setMatches] = useState(() => {
|
|
329
|
+
if (typeof window !== 'undefined') {
|
|
330
|
+
return window.matchMedia(query).matches;
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
const mediaQuery = window.matchMedia(query);
|
|
337
|
+
|
|
338
|
+
const handleChange = (event: MediaQueryListEvent) => {
|
|
339
|
+
setMatches(event.matches);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Definir valor inicial
|
|
343
|
+
setMatches(mediaQuery.matches);
|
|
344
|
+
|
|
345
|
+
// Escutar mudanças
|
|
346
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
347
|
+
|
|
348
|
+
return () => {
|
|
349
|
+
mediaQuery.removeEventListener('change', handleChange);
|
|
350
|
+
};
|
|
351
|
+
}, [query]);
|
|
352
|
+
|
|
353
|
+
return matches;
|
|
354
|
+
};
|
|
355
|
+
```
|
|
356
|
+
|
|
110
357
|
## Template de Teste
|
|
111
358
|
|
|
112
359
|
```typescript
|
|
@@ -116,28 +363,182 @@ import { describe, it, expect } from 'vitest';
|
|
|
116
363
|
import { useCounter } from './useCounter';
|
|
117
364
|
|
|
118
365
|
describe('useCounter', () => {
|
|
119
|
-
it('
|
|
366
|
+
it('should initialize with default value', () => {
|
|
120
367
|
const { result } = renderHook(() => useCounter());
|
|
368
|
+
|
|
121
369
|
expect(result.current.count).toBe(0);
|
|
122
370
|
});
|
|
123
371
|
|
|
124
|
-
it('
|
|
372
|
+
it('should initialize with custom value', () => {
|
|
373
|
+
const { result } = renderHook(() => useCounter({ initialValue: 10 }));
|
|
374
|
+
|
|
375
|
+
expect(result.current.count).toBe(10);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should increment count', () => {
|
|
125
379
|
const { result } = renderHook(() => useCounter());
|
|
380
|
+
|
|
126
381
|
act(() => {
|
|
127
382
|
result.current.increment();
|
|
128
383
|
});
|
|
384
|
+
|
|
129
385
|
expect(result.current.count).toBe(1);
|
|
130
386
|
});
|
|
387
|
+
|
|
388
|
+
it('should decrement count', () => {
|
|
389
|
+
const { result } = renderHook(() => useCounter({ initialValue: 5 }));
|
|
390
|
+
|
|
391
|
+
act(() => {
|
|
392
|
+
result.current.decrement();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
expect(result.current.count).toBe(4);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('should reset to initial value', () => {
|
|
399
|
+
const { result } = renderHook(() => useCounter({ initialValue: 0 }));
|
|
400
|
+
|
|
401
|
+
act(() => {
|
|
402
|
+
result.current.increment();
|
|
403
|
+
result.current.increment();
|
|
404
|
+
result.current.reset();
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
expect(result.current.count).toBe(0);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('should respect max value', () => {
|
|
411
|
+
const { result } = renderHook(() => useCounter({ initialValue: 9, max: 10 }));
|
|
412
|
+
|
|
413
|
+
act(() => {
|
|
414
|
+
result.current.increment();
|
|
415
|
+
result.current.increment(); // Não deve ultrapassar o max
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
expect(result.current.count).toBe(10);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('should respect min value', () => {
|
|
422
|
+
const { result } = renderHook(() => useCounter({ initialValue: 1, min: 0 }));
|
|
423
|
+
|
|
424
|
+
act(() => {
|
|
425
|
+
result.current.decrement();
|
|
426
|
+
result.current.decrement(); // Não deve ficar abaixo do min
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
expect(result.current.count).toBe(0);
|
|
430
|
+
});
|
|
131
431
|
});
|
|
132
432
|
```
|
|
133
433
|
|
|
134
|
-
##
|
|
434
|
+
## Padrões de Hook
|
|
435
|
+
|
|
436
|
+
### Composição
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// useAuth.ts
|
|
440
|
+
export const useAuth = () => {
|
|
441
|
+
const user = useUser();
|
|
442
|
+
const permissions = usePermissions(user?.id);
|
|
443
|
+
const logout = useLogout();
|
|
444
|
+
|
|
445
|
+
return {
|
|
446
|
+
user,
|
|
447
|
+
permissions,
|
|
448
|
+
logout,
|
|
449
|
+
isAuthenticated: !!user
|
|
450
|
+
};
|
|
451
|
+
};
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Injeção de Dependência
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
// useRepository.ts
|
|
458
|
+
export const useUserRepository = () => {
|
|
459
|
+
const apiClient = useApiClient();
|
|
460
|
+
|
|
461
|
+
return useMemo(
|
|
462
|
+
() => createUserRepository(apiClient),
|
|
463
|
+
[apiClient]
|
|
464
|
+
);
|
|
465
|
+
};
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Handlers de Eventos
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
// useClickOutside.ts
|
|
472
|
+
export const useClickOutside = (
|
|
473
|
+
ref: RefObject<HTMLElement>,
|
|
474
|
+
handler: () => void
|
|
475
|
+
) => {
|
|
476
|
+
useEffect(() => {
|
|
477
|
+
const listener = (event: MouseEvent | TouchEvent) => {
|
|
478
|
+
if (!ref.current || ref.current.contains(event.target as Node)) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
handler();
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
document.addEventListener('mousedown', listener);
|
|
485
|
+
document.addEventListener('touchstart', listener);
|
|
486
|
+
|
|
487
|
+
return () => {
|
|
488
|
+
document.removeEventListener('mousedown', listener);
|
|
489
|
+
document.removeEventListener('touchstart', listener);
|
|
490
|
+
};
|
|
491
|
+
}, [ref, handler]);
|
|
492
|
+
};
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Script Gerador
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
// scripts/generate-hook.ts
|
|
499
|
+
import fs from 'fs/promises';
|
|
500
|
+
import path from 'path';
|
|
501
|
+
|
|
502
|
+
async function generateHook(name: string, hookPath = 'src/hooks') {
|
|
503
|
+
const fileName = name.startsWith('use') ? name : `use${name}`;
|
|
504
|
+
const dir = path.join(hookPath);
|
|
505
|
+
|
|
506
|
+
await fs.mkdir(dir, { recursive: true });
|
|
135
507
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
508
|
+
// Gerar arquivo do hook
|
|
509
|
+
await fs.writeFile(
|
|
510
|
+
path.join(dir, `${fileName}.ts`),
|
|
511
|
+
getHookTemplate(fileName)
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
// Gerar arquivo de teste
|
|
515
|
+
await fs.writeFile(
|
|
516
|
+
path.join(dir, `${fileName}.test.ts`),
|
|
517
|
+
getTestTemplate(fileName)
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
console.log(`✅ Hook ${fileName} created at ${dir}`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Executar
|
|
524
|
+
const [,, name] = process.argv;
|
|
525
|
+
generateHook(name);
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## Boas Práticas
|
|
529
|
+
|
|
530
|
+
1. **Nomenclatura**: Sempre iniciar com o prefixo `use`
|
|
531
|
+
2. **Responsabilidade Única**: Um hook, um propósito
|
|
532
|
+
3. **Dependências**: Listar todas as dependências em useEffect/useCallback
|
|
533
|
+
4. **TypeScript**: Tipagem forte para valores de retorno e parâmetros
|
|
534
|
+
5. **Documentação**: Comentários JSDoc com exemplos
|
|
141
535
|
6. **Testes**: Testar todos os casos de uso e casos extremos
|
|
142
|
-
7. **
|
|
143
|
-
8. **Tratamento de Erros**: Tratar erros
|
|
536
|
+
7. **Memoização**: Usar useMemo/useCallback de forma adequada
|
|
537
|
+
8. **Tratamento de Erros**: Tratar erros de forma elegante
|
|
538
|
+
|
|
539
|
+
## Recursos
|
|
540
|
+
|
|
541
|
+
- [Documentação React Hooks](https://react.dev/reference/react)
|
|
542
|
+
- [Guia de Hooks Personalizados](https://react.dev/learn/reusing-logic-with-custom-hooks)
|
|
543
|
+
- [Testing Library Hooks](https://react-hooks-testing-library.com/)
|
|
544
|
+
- [usehooks.com](https://usehooks.com/)
|