Skip to content

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 existe

Toast 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 feedback

Invalidaçã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 filtros
  • enabled: isAuthenticated && papelPrincipal === 'administrador' — guarda de acesso
  • staleTime: 5 * 60 * 1000 — 5 min de cache
  • mutateAsync em funções de conveniência com try/catch
  • invalidateQueries no onSuccess da mutation
  • Toast no onSuccess e onError

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 chamadas
  • src/components/ui/use-olp-toast.tsx — Toast padronizado
  • src/hooks/useAdminEscolas.ts — Exemplo de referência
  • REACT_QUERY_CACHE.md — Critérios de cache obrigatório e tabela de staleTime