Capítulo 11 de 456

Error Handling

Core Idea

Divide erros em duas categorias com tratamento distinto: erros esperados (validação, request que falhou) modelados como valores de retorno, e exceções não capturadas (bugs) tratadas por error boundaries.

Key Concepts

  • Erros esperados: ocorrem no funcionamento normal (validação de formulário, request falho); devem ser modelados como valores de retorno, não throw.
  • useActionState: hook React usado com Server Functions para capturar o estado retornado (incluindo mensagem de erro) e refletir na UI.
  • notFound(): função chamada dentro de um route segment para renderizar a UI de not-found.js (404) daquele segmento.
  • Error boundary (error.js): arquivo de convenção que cria uma boundary de erro para um route segment; deve ser Client Component ('use client'); recebe error e retry.
  • catchError: função (next/error) que cria error boundaries reutilizáveis para qualquer parte da árvore de componentes, não só no nível de rota.
  • global-error.js: arquivo na raiz de app para tratar erros no root layout; precisa definir suas próprias tags <html> e <body> porque substitui o root layout quando ativo.
  • Erros em event handlers / async code: NÃO são capturados por error boundaries (só erros durante render); precisam de try/catch manual + useState/useReducer.
  • useTransition / startTransition: erros não tratados dentro de startTransition sobem até a error boundary mais próxima (exceção à regra acima).

Code Examples

'use server'

export async function createPost(prevState: any, formData: FormData) {
  const res = await fetch('https://api.vercel.app/posts', {
    method: 'POST',
    body: { title: formData.get('title'), content: formData.get('content') },
  })
  if (!res.ok) {
    return { message: 'Failed to create post' }
  }
}
  • O que demonstra: erro esperado modelado como valor de retorno em Server Function, sem throw.
'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions'

export function Form() {
  const [state, formAction, pending] = useActionState(createPost, { message: '' })
  return (
    <form action={formAction}>
      {state?.message && <p aria-live="polite">{state.message}</p>}
      <button disabled={pending}>Create Post</button>
    </form>
  )
}
  • O que demonstra: consumir o estado de erro retornado via useActionState e exibir na UI.
'use client'
import { useEffect } from 'react'

export default function ErrorPage({ error, retry }: { error: Error & { digest?: string }; retry: () => void }) {
  useEffect(() => { console.error(error) }, [error])
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => retry()}>Try again</button>
    </div>
  )
}
  • O que demonstra: app/dashboard/error.tsx como error boundary padrão de route segment, com log e recuperação via retry().
'use client'
import { catchError, type ErrorInfo } from 'next/error'

function ErrorFallback(props: { title: string }, { error, retry }: ErrorInfo) {
  return (
    <div>
      <h2>{props.title}</h2>
      <p>{error.message}</p>
      <button onClick={() => retry()}>Try again</button>
    </div>
  )
}

export default catchError(ErrorFallback)
  • O que demonstra: error boundary componentizável com catchError, reusável em qualquer ponto da árvore (não só rota inteira).

Anti-patterns

  • Usar try/catch + throw para erros esperados: dificulta exibir a mensagem na UI; modele como retorno de estado.
  • Confiar em error boundary para erro de event handler: error boundaries só capturam erros durante render; capture manualmente com try/catch e useState.
  • Esquecer 'use client' em error.js/global-error.js: error boundaries precisam ser Client Components.

Key Takeaways

  1. Erro esperado (validação, request falho) = valor de retorno + useActionState; exceção não capturada = throw + error boundary.
  2. error.js cobre um route segment e propaga para o boundary pai mais próximo se não existir um local; global-error.js cobre o root layout inteiro e precisa de <html>/<body> próprios.
  3. catchError permite boundaries granulares em qualquer componente, não só por segmento de rota.
  4. Erros em startTransition sobem para a error boundary; erros em handlers de evento comuns não sobem, precisam de captura manual.
  5. notFound() + not-found.js é o padrão para 404 específico de um segmento (ex.: slug de post inexistente).

Connects To

  • Route Handlers (ch016): erros retornados de Route Handlers seguem princípio similar de resposta explícita em vez de exceção não tratada.