Límites de Error en RN
Patrones de interfaz de recuperación y fallback a nivel de pantalla.
Busca en todas las páginas de la documentación
Patrones de interfaz de recuperación y fallback a nivel de pantalla.
Los límites de error de React previenen que el fallo de un componente único deje en blanco toda la aplicación. En móvil, la granularidad correcta es usualmente un límite por pantalla (o diseño de Expo Router) con una interfaz de recuperación que permita al usuario reintentar o alejarse.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
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);
// Reenvía al reportero de crashes aquí (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}>Algo salió mal</Text>
<Text style={styles.body}>Esta pantalla encontró un error inesperado.</Text>
<Pressable onPress={this.reset} style={styles.button}>
<Text style={styles.buttonLabel}>Reintentar</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" },
});Cuándo usarlo:
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}>No pudimos cargar {this.props.screenName}</Text>
<Text style={styles.body}>
El resto de la aplicación aún funciona. Reintentar o volver a Inicio.
</Text>
<Pressable onPress={this.reset} style={styles.primary}>
<Text style={styles.primaryLabel}>Reintentar</Text>
</Pressable>
<Pressable onPress={this.props.onNavigateHome} style={styles.secondary}>
<Text style={styles.secondaryLabel}>Ir a Inicio</Text>
</Pressable>
</SafeAreaView>
);
}
return this.props.children;
}
}
/** Widget de demostración que lanza cuando count es múltiplo de 3 - simula una ruta de renderizado defectuosa. */
function FlakyCounter() {
const [count, setCount] = useState(0);
if (count > 0 && count % 3 === 0) {
throw new Error(`Render failed at count ${count}`);
}
return (
<View style={styles.card}>
<Text style={styles.cardTitle}>Widget inestable</Text>
<Text style={styles.count}>{count}</Text>
<Pressable onPress={() => setCount((c) => c + 1)} style={styles.primary}>
<Text style={styles.primaryLabel}>Incrementar (se cuelga cada 3er 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}>Inicio</Text>
<Text style={styles.body}>Escapaste de una pantalla rota de forma segura.</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" },
});Lo que esto demuestra:
FeedScreen mientras HomeScreen se mantiene saludable.reset limpia el estado del límite) e Ir a Inicio (salida de navegación).componentDidCatch registra el error y la pila de componentes - el gancho para reporteros de crashes.SafeAreaView en el fallback mantiene el chrome de recuperación libre de muescas e indicadores de inicio.getDerivedStateFromError se ejecuta primero - retorna estado nuevo (p.ej. hasError: true) para que el próximo renderizado muestre la interfaz de fallback en lugar de relanzar.componentDidCatch se ejecuta después del commit - úsalo para logging, analytics y reportes de crash. No retorna JSX.hasError: false) para que React intente un renderizado normal nuevamente. Empareja reset con refetching de datos o remontaje de hijos mediante una key cuando el estado obsoleto causó el crash.react-error-boundary) es normal.| Capturado | No capturado |
|---|---|
| Errores en renderizado de hijo | Errores en el renderizado del límite mismo |
| Métodos de ciclo de vida del hijo | Manejadores de eventos (onPress, etc.) |
| Constructores de hijos | setTimeout, fetch, async/await |
| Errores en límites hijos inferiores | Renderizado del lado del servidor (N/A en RN) |
// NO capturado - maneja con try/catch dentro del manejador
function BadButton() {
return (
<Pressable
onPress={() => {
throw new Error("Handler throw");
}}
>
<Text>Tócame</Text>
</Pressable>
);
}
// Capturado - lanza durante el renderizado
function BadRender({ value }: { value: string | null }) {
if (!value) throw new Error("Missing value");
return <Text>{value}</Text>;
}Los errores asíncronos y de manejador de eventos pertenecen a try/catch en el límite de API o en manejadores globales - ve Manejadores de Error Globales.
Envuelve en el diseño o exportación de pantalla - un límite por ruta, no alrededor de cada nodo 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>
);
}Elige un propietario por pantalla - envoltorio de diseño o envoltorio de pantalla, no ambos (evita fallbacks dobles).
| Patrón | Cuándo | Implementación |
|---|---|---|
| Reintentar / reset | Estado malo transitorio o error de renderizado único | setState({ hasError: false }) + refetch |
| Remontar hijos | Estado de hijo corrompido | Cambia key en hijos cuando reseteas |
| Navegar lejos | La pantalla no puede autorrepararse | router.replace("/") o cambio de pestaña |
| Modo reducido | El módulo opcional falló | El límite interior muestra tarjeta "no disponible" en línea |
| Enlace de soporte | Fallos repetidos | Linking.openURL al centro de ayuda |
function ScreenErrorBoundary({ children, resetKey }: { children: ReactNode; resetKey?: number }) {
// Pasa key={resetKey} al envoltorio de hijos para forzar remontaje en reset
return <RouteErrorBoundary key={resetKey}>{children}</RouteErrorBoundary>;
}import type { ErrorInfo, ReactNode } from "react";
type FallbackRender = (args: {
error: Error;
reset: () => void;
}) => ReactNode;
// ErrorInfo.componentStack es la ruta de árbol de componentes React - no la traza de pila 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, opcional fallbackRender, opcional onReset.Error en estado para interfaz; almacena un mensaje seguro para el usuario. Registra el error completo en componentDidCatch.Esperar que los límites capturen lanzamientos onPress - Los manejadores de eventos se ejecutan fuera de la ruta de renderizado. Solución: Envuelve cuerpos de manejador en try/catch y muestra un toast o error en línea.
Un límite alrededor de toda la aplicación solamente - Una sola hoja rota derriba toda la navegación con un fallback genérico. Solución: Agrega límites por pantalla; mantén un límite raíz como último recurso.
Reset sin remontar o refetch - El mismo estado corrupto relanza inmediatamente en reintentar. Solución: Golpea una key en hijos o refetch de datos de pantalla dentro de onReset.
Mostrando error.message o trazas de pila a usuarios - Filtra detalles de implementación y confunde a usuarios no técnicos. Solución: Copia genérica en fallback; registra detalles en componentDidCatch.
Lanzar dentro del render del límite mismo - El error se propaga al límite padre (o cuelga la aplicación). Solución: Mantén el JSX de fallback trivial; sin fetching de datos en el componente de límite.
Componentes funcionales "límites" - Los hooks no pueden implementar getDerivedStateFromError. Solución: Usa un componente de clase o el paquete react-error-boundary.
Logging faltante en componentDidCatch - Los crashes de producción se vuelven invisibles. Solución: Siempre registra y reenvía a tu reportero de crash antes de llamar reset.
| Alternativa | Úsalo Cuando | No Lo Uses Cuando |
|---|---|---|
Clase ErrorBoundary (esta página) | Control total, cero dependencias extra | Quieres un API render-prop de FallbackComponent lista para usar |
react-error-boundary | FallbackComponent declarativo, onReset, resetKeys | El tamaño del bundle es extremadamente limitado y una clase de 30 líneas es suficiente |
| try/catch en capa API asíncrona | fetch, mutaciones, llamadas de módulo nativo | Fallos en tiempo de renderizado desde props malos o componentes de terceros |
Manejador ErrorUtils global | Fatales nativos/JS sin manejar fuera de React | Reemplazando fallbacks contenidos a nivel de pantalla |
| Feature flags / null guards | Rutas de código conocidas-malas que puedes deshabilitar remotamente | Lanzamientos inesperados que no puedes predecir en tiempo de compilación |
React solo llama a getDerivedStateFromError y componentDidCatch en instancias de clase hoy (incluyendo React 19). No hay hook useErrorBoundary en React central. Usa una pequeña clase o una biblioteca que envuelva una.
Envuelve el contenido de cada pantalla - en app/.../index.tsx o el _layout.tsx de la ruta. Un límite por pantalla es el patrón móvil usual. Evita envolver cada elemento de lista.
Establece hasError de nuevo a false, para que React re-renderice hijos. Para una recuperación confiable, también refetch de datos o cambia una key en hijos para remontar un subárbol corrompido.
No. Los errores de red dentro de useEffect o funciones async no son errores de renderizado. Manéjalos con try/catch, tipos Result, o isError de TanStack Query - ve UX de Fallo de Red.
Sí, como red de seguridad - pero también agrega límites a nivel de pantalla para que una característica rota no bloquee la navegación al resto de la aplicación.
componentDidCatch(error: Error, info: ErrorInfo) {
console.error(error, info.componentStack);
// crashReporter.captureException(error, { extra: { componentStack: info.componentStack } });
}Reenvía en componentDidCatch; nunca confíes en que los usuarios reporten crashes de renderizado manualmente.
Solo para filas costosas o de terceros que fallen independientemente (p.ej. una celda de mapa). No envuelvas cada fila - agrega overhead y registros ruidosos. Prefiere null guards para datos de lista simples.
info.componentStack es la ruta de árbol de componentes de React (cuál <FeedList> renderizó cuál hijo). La traza de pila JS muestra funciones y archivos. Registra ambos en componentDidCatch para triaje.
React 19 mantiene el mismo API de límite. React Native 0.86 aún muestra errores de renderizado no capturados a través del redbox de desarrollo y a través de rutas de crash nativas en lanzamiento. Los límites permanecen como la capa de contención idiomática.
Los límites de error capturan errores de renderizado en árboles suspendidos una vez que el contenido se compromete. No reemplazan estados de carga - empareja límites con fallbacks de Suspense para carga, límites para fallo.
Solo cuando la pantalla es específica de auth y el estado de sesión corrupto es una causa probable. Para feeds generales o configuración, prefiere reintentar y navegar-a-inicio sobre cerrar la sesión del usuario automáticamente.
render() {
if (this.state.hasError) {
return this.props.fallback ?? <DefaultFallback onReset={this.reset} />;
}
return this.props.children;
}Un render-prop o prop fallback mantiene el límite reutilizable en pantallas con diferente diseño visual.
Sí - envuelve el contenido modal en su propio límite para que un modal roto no cuelgue la pantalla debajo. Reset o descarta el modal en errores irrecuperables.
Usa try/catch para código asíncrono e imperativo; usa límites de error para fallos en tiempo de renderizado. Juntos cubren la mayoría de modos de fallo del lado del cliente - ve Error Handling Basics.
componentDidCatch a reportes de crashVersiones de Stack: Esta página fue escrita para React 19.2.3, React Native 0.86.0, y Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última actualización: 16 jul 2026