Error Boundaries em RN
Fallback UI em nível de tela e padrões de recuperação.
Busque em todas as páginas da documentação
Fallback UI em nível de tela e padrões de recuperação.
Os Error Boundaries do React evitam que a falha de um único componente apague o aplicativo inteiro. Em dispositivos móveis, a granularidade correta é geralmente um boundary por tela (ou layout do Expo Router) com uma UI de recuperação que permite ao usuário tentar novamente ou navegar para longe.
Cartão de receita de referência rápida - pronto para copiar e colar.
import React, { Component, type ErrorInfo, type ReactNode } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
type Props = { children: ReactNode; onReset?: () => void };
type State = { hasError: boolean; message?: string };
export class ScreenErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, message: error.message };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error("[ScreenErrorBoundary]", error, info.componentStack);
// Encaminhe para o relator de falhas aqui (Sentry, etc.)
}
private reset = () => {
this.setState({ hasError: false, message: undefined });
this.props.onReset?.();
};
render() {
if (this.state.hasError) {
return (
<View style={styles.fallback}>
<Text style={styles.title}>Ocorreu um erro</Text>
<Text style={styles.body}>Esta tela encontrou um erro inesperado.</Text>
<Pressable onPress={this.reset} style={styles.button}>
<Text style={styles.buttonLabel}>Tentar novamente</Text>
</Pressable>
</View>
);
}
return this.props.children;
}
}
const styles = StyleSheet.create({
fallback: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 },
title: { fontSize: 20, fontWeight: "700", marginBottom: 8 },
body: { fontSize: 16, color: "#64748b", textAlign: "center", marginBottom: 20 },
button: { backgroundColor: "#2563eb", paddingHorizontal: 20, paddingVertical: 12, borderRadius: 8 },
buttonLabel: { color: "#fff", fontWeight: "600" },
});Quando usar isso:
import React, { Component, type ErrorInfo, type ReactNode, useState } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
type BoundaryProps = {
children: ReactNode;
screenName: string;
onNavigateHome?: () => void;
};
type BoundaryState = { hasError: boolean };
class RouteErrorBoundary extends Component<BoundaryProps, BoundaryState> {
state: BoundaryState = { hasError: false };
static getDerivedStateFromError(): BoundaryState {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error(`[${this.props.screenName}]`, error.message, info.componentStack);
}
private reset = () => this.setState({ hasError: false });
render() {
if (this.state.hasError) {
return (
<SafeAreaView style={styles.fallback} edges={["top", "bottom"]}>
<Text style={styles.title}>Não foi possível carregar {this.props.screenName}</Text>
<Text style={styles.body}>
O restante do aplicativo ainda funciona. Tente novamente ou volte para a Home.
</Text>
<Pressable onPress={this.reset} style={styles.primary}>
<Text style={styles.primaryLabel}>Tentar novamente</Text>
</Pressable>
<Pressable onPress={this.props.onNavigateHome} style={styles.secondary}>
<Text style={styles.secondaryLabel}>Ir para Home</Text>
</Pressable>
</SafeAreaView>
);
}
return this.props.children;
}
}
/** Widget de demonstração que lança um erro quando a contagem é múltipla de 3 - simula um caminho de renderização ruim. */
function FlakyCounter() {
const [count, setCount] = useState(0);
if (count > 0 && count % 3 === 0) {
throw new Error(`Render falhou na contagem ${count}`);
}
return (
<View style={styles.card}>
<Text style={styles.cardTitle}>Widget instável</Text>
<Text style={styles.count}>{count}</Text>
<Pressable onPress={() => setCount((c) => c + 1)} style={styles.primary}>
<Text style={styles.primaryLabel}>Incrementar (trava a cada 3ª toque)</Text>
</Pressable>
</View>
);
}
function FeedScreen({ onNavigateHome }: { onNavigateHome: () => void }) {
return (
<RouteErrorBoundary screenName="Feed" onNavigateHome={onNavigateHome}>
<View style={styles.screen}>
<Text style={styles.screenTitle}>Feed</Text>
<FlakyCounter />
</View>
</RouteErrorBoundary>
);
}
function HomeScreen() {
return (
<View style={styles.screen}>
<Text style={styles.screenTitle}>Home</Text>
<Text style={styles.body}>Você escapou de uma tela quebrada com segurança.</Text>
</View>
);
}
export default function App() {
const [route, setRoute] = useState<"feed" | "home">("feed");
return (
<SafeAreaProvider>
{route === "feed" ? (
<FeedScreen onNavigateHome={() => setRoute("home")} />
) : (
<HomeScreen />
)}
</SafeAreaProvider>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 20, backgroundColor: "#f8fafc" },
screenTitle: { fontSize: 28, fontWeight: "800", marginBottom: 16 },
card: { padding: 20, borderRadius: 12, backgroundColor: "#fff", gap: 12 },
cardTitle: { fontSize: 18, fontWeight: "600" },
count: { fontSize: 32, fontWeight: "700" },
fallback: { flex: 1, justifyContent: "center", padding: 24, backgroundColor: "#fff" },
title: { fontSize: 22, fontWeight: "700", marginBottom: 8 },
body: { fontSize: 16, lineHeight: 24, color: "#64748b", marginBottom: 20 },
primary: { backgroundColor: "#2563eb", padding: 14, borderRadius: 8, alignItems: "center" },
primaryLabel: { color: "#fff", fontWeight: "600" },
secondary: { marginTop: 12, padding: 14, alignItems: "center" },
secondaryLabel: { color: "#2563eb", fontWeight: "600" },
});O que isso demonstra:
FeedScreen enquanto a HomeScreen permanece saudável.reset limpa o estado do boundary) e Ir para Home (saída de navegação).componentDidCatch registra o erro e a pilha de componentes - o hook para relatadores de falhas.SafeAreaView no fallback mantém a interface de recuperação livre de entalhes e indicadores de home.getDerivedStateFromError é executado primeiro - retorne um novo estado (por exemplo, hasError: true) para que a próxima renderização mostre a UI de fallback em vez de relançar.componentDidCatch é executado após o commit - use-o para registro, análise e relatórios de falhas. Ele não retorna JSX.hasError: false) para que o React tente uma renderização normal novamente. Emparelhe o reset com a busca de dados novamente ou a remontagem de filhos via uma key quando o estado obsoleto causou a falha.react-error-boundary) é normal.| Capturado | Não capturado |
|---|---|
| Erros na renderização de filhos | Erros na própria renderização do boundary |
| Métodos de ciclo de vida de filhos | Manipuladores de eventos (onPress, etc.) |
| Construtores de filhos | setTimeout, fetch, async/await |
| Erros em boundaries filhos abaixo | Renderização do lado do servidor (N/A em RN) |
// NÃO capturado - trate com try/catch dentro do manipulador
function BadButton() {
return (
<Pressable
onPress={() => {
throw new Error("Erro no manipulador");
}}
>
<Text>Toque em mim</Text>
</Pressable>
);
}
// Capturado - lança durante a renderização
function BadRender({ value }: { value: string | null }) {
if (!value) throw new Error("Valor ausente");
return <Text>{value}</Text>;
}Erros assíncronos e de manipuladores de eventos pertencem a try/catch na fronteira da API ou em manipuladores globais - veja Manipuladores Globais de Erro.
Envolva no layout ou na exportação da tela - um boundary por rota, não em torno de cada nó Text.
// app/(tabs)/feed/_layout.tsx
import { Stack } from "expo-router";
import { ScreenErrorBoundary } from "@/components/ScreenErrorBoundary";
export default function FeedLayout() {
return (
<ScreenErrorBoundary screenName="Feed">
<Stack screenOptions={{ headerShown: false }} />
</ScreenErrorBoundary>
);
}// app/(tabs)/feed/index.tsx
import { ScreenErrorBoundary } from "@/components/ScreenErrorBoundary";
import { FeedScreenContent } from "@/features/feed/FeedScreenContent";
export default function FeedRoute() {
return (
<ScreenErrorBoundary screenName="Feed">
<FeedScreenContent />
</ScreenErrorBoundary>
);
}Escolha um proprietário por tela - wrapper de layout ou wrapper de tela, não ambos (evita fallbacks duplos).
| Padrão | Quando | Implementação |
|---|---|---|
| Tentar novamente / resetar | Estado ruim transitório ou bug de renderização pontual | setState({ hasError: false }) + refetch |
| Remontar filhos | Estado corrompido do filho | Mude a key nos filhos ao resetar |
| Navegar para longe | A tela não pode se curar | router.replace("/") ou troca de aba |
| Modo reduzido | Módulo opcional falhou | Boundary interno mostra cartão inline "indisponível" |
| Link de suporte | Falhas repetidas | Linking.openURL para o centro de ajuda |
function ScreenErrorBoundary({ children, resetKey }: { children: ReactNode; resetKey?: number }) {
// Passe key={resetKey} para o wrapper de filhos para forçar a remontagem no reset
return <RouteErrorBoundary key={resetKey}>{children}</RouteErrorBoundary>;
}import type { ErrorInfo, ReactNode } from "react";
type FallbackRender = (args: {
error: Error;
reset: () => void;
}) => ReactNode;
// ErrorInfo.componentStack é a pilha de componentes React - não o stack trace JS
function logBoundaryError(error: Error, info: ErrorInfo, screen: string) {
const payload = {
screen,
message: error.message,
componentStack: info.componentStack,
};
console.error(JSON.stringify(payload));
}children, screenName, fallbackRender opcional, onReset opcional.Error no estado para a UI; armazene uma mensagem segura para o usuário. Registre o erro completo em componentDidCatch.Esperar que os boundaries capturem lançamentos de onPress - Manipuladores de eventos são executados fora do caminho de renderização. Correção: Envolva os corpos dos manipuladores em try/catch e exiba um toast ou um erro inline.
Um boundary em torno de todo o aplicativo apenas - Uma única folha quebrada derruba toda a navegação com um fallback genérico. Correção: Adicione boundaries por tela; mantenha um boundary raiz como último recurso.
Resetar sem remontar ou refetching - O mesmo estado corrompido lança novamente imediatamente na retentativa. Correção: Incremente uma key nos filhos ou refetch os dados da tela dentro de onReset.
Mostrar error.message ou stack traces para usuários - Vazamentos de detalhes de implementação e confundem usuários não técnicos. Correção: Cópia genérica no fallback; registre detalhes em componentDidCatch.
Lançar erros dentro da própria render do boundary - O erro se propaga para o boundary pai (ou trava o aplicativo). Correção: Mantenha o JSX de fallback trivial; sem busca de dados no componente boundary.
"Boundaries" de componentes funcionais - Hooks não podem implementar getDerivedStateFromError. Correção: Use um componente de classe ou o pacote react-error-boundary.
Registro ausente em componentDidCatch - Falhas de produção se tornam invisíveis. Correção: Sempre registre e encaminhe para seu relatador de falhas antes de chamar reset.
| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
Classe ErrorBoundary (esta página) | Controle total, sem dependências extras | Você quer uma API de render-prop FallbackComponent pronta para uso |
react-error-boundary | FallbackComponent declarativo, onReset, resetKeys | O tamanho do bundle é extremamente restrito e uma classe de 30 linhas é suficiente |
| try/catch na camada de API assíncrona | fetch, mutações, chamadas de módulo nativo | Falhas em tempo de renderização de props ruins ou componentes de terceiros |
Manipulador ErrorUtils global | Fatais não capturados e rejeições não tratadas fora do React | Substituir fallbacks contidos em nível de tela |
| Flags de recursos / guardas nulas | Caminhos de código conhecidamente ruins que você pode desabilitar remotamente | Lançamentos inesperados que você não pode prever em tempo de compilação |
O React só chama getDerivedStateFromError e componentDidCatch em instâncias de classe hoje (incluindo React 19). Não há um hook useErrorBoundary no core do React. Use uma pequena classe ou uma biblioteca que a envolva.
Envolva o conteúdo de cada tela - em app/.../index.tsx ou no _layout.tsx da rota. Um boundary por tela é o padrão móvel usual. Evite envolver cada item da lista.
Ele define hasError de volta para false, para que o React renderize os filhos novamente. Para uma recuperação confiável, também refetch os dados ou altere uma key nos filhos para remontar uma subárvore corrompida.
Não. Erros de rede dentro de useEffect ou funções async não são erros de renderização. Trate-os com try/catch, tipos Result ou TanStack Query isError - veja UX de Falha de Rede.
Sim, como uma rede de segurança - mas também adicione boundaries em nível de tela para que um recurso quebrado não bloqueie a navegação para o resto do aplicativo.
componentDidCatch(error: Error, info: ErrorInfo) {
console.error(error, info.componentStack);
// crashReporter.captureException(error, { extra: { componentStack: info.componentStack } });
}Encaminhe em componentDidCatch; nunca confie nos usuários para relatar falhas de renderização manualmente.
Apenas para linhas caras ou de terceiros que falham independentemente (por exemplo, uma célula de mapa). Não envolva todas as linhas - isso adiciona sobrecarga e logs barulhentos. Prefira null guards para dados simples de lista.
info.componentStack é o caminho da árvore de componentes do React (qual <FeedList> renderizou qual filho). O stack trace JS mostra funções e arquivos. Registre ambos em componentDidCatch para triagem.
O React 19 mantém a mesma API de boundary. O React Native 0.86 ainda exibe erros de renderização não capturados através da caixa vermelha de desenvolvimento e através de caminhos de falha nativos em releases. Boundaries permanecem a camada de contenção idiomática.
Error boundaries capturam erros de renderização em árvores suspensas assim que o conteúdo é commitado. Eles não substituem os estados de carregamento - emparelhe boundaries com fallbacks de Suspense para carregamento, boundaries para falha.
Apenas quando a tela é específica de autenticação e o estado de sessão corrompido é uma causa provável. Para feeds ou configurações gerais, prefira tentar novamente e navegar para Home em vez de deslogar o usuário automaticamente.
render() {
if (this.state.hasError) {
return this.props.fallback ?? <DefaultFallback onReset={this.reset} />;
}
return this.props.children;
}Um render-prop ou prop fallback mantém o boundary reutilizável em telas com design visual diferente.
Sim - envolva o conteúdo do modal em seu próprio boundary para que um modal quebrado não trave a tela abaixo dele. Reinicie ou feche o modal em erros irrecuperáveis.
Use try/catch para código assíncrono e imperativo; use error boundaries para falhas em tempo de renderização. Juntos, eles cobrem a maioria dos modos de falha do lado do cliente - veja Fundamentos de Tratamento de Erros.
componentDidCatch a relatórios de falhasVersões de Stack: Esta página foi escrita para React 19.2.3, React Native 0.86.0 e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026