Guia: Criar Novo Hook React
Template e checklist para hooks que consomem Edge Functions via
invokeAction/invokeEdge
1. Template Base
typescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useAuth } from '@/contexts/auth-context'
import { invokeAction } from '@/lib/edge-function'
import { olpToast } from '@/components/ui/use-olp-toast'
import { getUserFriendlyError } from '@/lib/error-helpers'
// 1. Definir interfaces de dados
export interface MeuItem {
id: string
nome: string
// ...
}
interface CreateMeuItemData {
nome: string
// ...
}
// 2. Hook principal
export function useMeuModulo() {
const { isAuthenticated, papelPrincipal } = useAuth()
const queryClient = useQueryClient()
// ── Query (leitura) ──────────────────────────
const {
data: items = [],
isLoading,
error,
refetch,
} = useQuery<MeuItem[]>({
queryKey: ['meu-modulo'],
queryFn: async () => {
const result = await invokeAction('minha-edge-function', 'list', {})
if (!result.success) {
throw new Error(result.message || 'Erro ao carregar')
}
return result.data || []
},
enabled: isAuthenticated && papelPrincipal === 'papel_requerido',
staleTime: 5 * 60 * 1000, // 5 min
refetchOnWindowFocus: false,
})
// ── Mutation (criação) ──────────────────────────
const createMutation = useMutation({
mutationFn: async (data: CreateMeuItemData) => {
const result = await invokeAction('minha-edge-function', 'create', data)
if (!result.success) {
throw new Error(result.message || 'Erro ao criar')
}
return result.data
},
onSuccess: () => {
olpToast.success('Item criado', { description: 'Operação realizada com sucesso.' })
queryClient.invalidateQueries({ queryKey: ['meu-modulo'] })
},
onError: (error: Error) => {
olpToast.error('Erro ao criar', { description: getUserFriendlyError(error) })
},
})
// ── Mutation (atualização) ──────────────────────────
const updateMutation = useMutation({
mutationFn: async ({ id, ...data }: CreateMeuItemData & { id: string }) => {
const result = await invokeAction('minha-edge-function', 'update', { id, ...data })
if (!result.success) {
throw new Error(result.message || 'Erro ao atualizar')
}
return result.data
},
onSuccess: () => {
olpToast.success('Item atualizado', { description: 'Os dados foram salvos.' })
queryClient.invalidateQueries({ queryKey: ['meu-modulo'] })
},
onError: (error: Error) => {
olpToast.error('Erro ao atualizar', { description: getUserFriendlyError(error) })
},
})
// ── Funções de conveniência ──────────────────────────
const criar = async (data: CreateMeuItemData) => {
try {
await createMutation.mutateAsync(data)
return { success: true }
} catch {
return { success: false }
}
}
const atualizar = async (id: string, data: CreateMeuItemData) => {
try {
await updateMutation.mutateAsync({ id, ...data })
return { success: true }
} catch {
return { success: false }
}
}
return {
items,
isLoading,
error,
refetch,
criar,
atualizar,
isCreating: createMutation.isPending,
isUpdating: updateMutation.isPending,
}
}2. Padrões Obrigatórios
Chamadas a Edge Functions
typescript
// invokeAction — body: { action, params: { ... } }
const result = await invokeAction('funcao', 'list', { filtro: 'x' })
// invokeEdge — body livre (flat)
const result = await invokeEdge('funcao', { action: 'list', filtro: 'x' })Resposta Padrão (EdgeResponse)
typescript
if (result.success && result.data) {
setItems(result.data) // ✅ CORRETO — usar result.data
}
// ❌ ERRADO — result.items NÃO existeToast Obrigatório
typescript
import { olpToast } from '@/components/ui/use-olp-toast'
olpToast.success('Título', { description: 'Descrição' })
olpToast.error('Título', { description: getUserFriendlyError(error) })
olpToast.info('Título', { description: 'Descrição' })
// ❌ PROIBIDO: alert(), console.log para feedbackInvalidação de Cache
typescript
// Invalida todas as queries com prefixo 'meu-modulo'
queryClient.invalidateQueries({ queryKey: ['meu-modulo'] })
// Invalidar queries relacionadas (ex: dashboard após criar escola)
queryClient.invalidateQueries({ queryKey: ['admin-dashboard'] })3. Exemplo Real: useAdminEscolas
Arquivo: src/hooks/useAdminEscolas.ts
Pontos de referência:
queryKey: ['admin-escolas', filters]— cache com filtrosenabled: isAuthenticated && papelPrincipal === 'administrador'— guarda de acessostaleTime: 5 * 60 * 1000— 5 min de cachemutateAsyncem funções de conveniência com try/catchinvalidateQueriesnoonSuccessda mutation- Toast no
onSuccesseonError
4. Checklist para Novo Hook
markdown
□ Arquivo criado em src/hooks/use<Modulo>.ts
□ Importa useAuth, invokeAction/invokeEdge, olpToast, useQuery/useMutation
□ Importa getUserFriendlyError de @/lib/error-helpers
□ useQuery para operações de leitura com queryKey único
□ useMutation para CREATE/UPDATE/DELETE
□ enabled condicionado a isAuthenticated + papel
□ invalidateQueries no onSuccess de cada mutation
□ olpToast.success no onSuccess
□ getUserFriendlyError(error) em TODOS os onError (NUNCA error.message cru)
□ Funções de conveniência com try/catch retornando { success: boolean }
□ Verificado critérios de cache (docs/development/REACT_QUERY_CACHE.md)Referências
src/lib/edge-function.ts— Helper de chamadassrc/components/ui/use-olp-toast.tsx— Toast padronizadosrc/hooks/useAdminEscolas.ts— Exemplo de referência- REACT_QUERY_CACHE.md — Critérios de cache obrigatório e tabela de staleTime