Límites de Error — Captura errores de renderizado en el árbol de componentes y muestra interfaz alternativa
Receta
import { Component, type ErrorInfo, type ReactNode } from "react";
class ErrorBoundary extends Component<
{ children: ReactNode; fallback: ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError(): { hasError: boolean } {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error("ErrorBoundary caught:", error, info.componentStack);
}
render() {
if (this.state.hasError) return this.props.fallback;
return this.props.children;
}
}
// Uso
<ErrorBoundary fallback={<p>Algo salió mal.</p>}>
<RiskyComponent />
</ErrorBoundary>Cuándo usarlo: Envuelve cualquier subárbol de componentes que podría lanzar durante el renderizado. Es esencial alrededor de componentes de búsqueda de datos, widgets de terceros, renderizadores de contenido generado por el usuario y diseños a nivel de ruta.
Ejemplo Funcional
import {
Component,
useState,
useCallback,
type ErrorInfo,
type ReactNode,
} from "react";
// --- Límite de error completo con recuperación ---
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
onError?: (error: Error, info: ErrorInfo) => void;
renderFallback?: (props: {
error: Error;
reset: () => void;
}) => ReactNode;
resetKeys?: unknown[];
}
interface ErrorBoundaryState {
error: Error | null;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
state: ErrorBoundaryState = { error: null };
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { error };
}
componentDidCatch(error: Error, info: ErrorInfo) {
this.props.onError?.(error, info);
}
componentDidUpdate(prevProps: ErrorBoundaryProps) {
if (this.state.error && prevProps.resetKeys !== this.props.resetKeys) {
// Si resetKeys cambió, intenta recuperación
const changed = this.props.resetKeys?.some(
(key, i) => key !== prevProps.resetKeys?.[i]
);
if (changed) this.reset();
}
}
reset = () => {
this.setState({ error: null });
};
render() {
const { error } = this.state;
const { children, fallback, renderFallback } = this.props;
if (error) {
if (renderFallback) {
return renderFallback({ error, reset: this.reset });
}
return fallback ?? <p>Algo salió mal.</p>;
}
return children;
}
}
// --- Uso con interfaz de recuperación ---
function UserProfile({ userId }: { userId: string }) {
return (
<ErrorBoundary
resetKeys={[userId]}
onError={(error) => {
// Reporta al servicio de seguimiento de errores
reportToSentry(error);
}}
renderFallback={({ error, reset }) => (
<div className="p-6 bg-red-50 rounded-lg text-center">
<h3 className="text-red-800 font-semibold">No se pudo cargar el perfil</h3>
<p className="text-red-600 mt-2">{error.message}</p>
<button
onClick={reset}
className="mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
>
Intentar de Nuevo
</button>
</div>
)}
>
<ProfileContent userId={userId} />
</ErrorBoundary>
);
}
// --- Límites de error granular por sección ---
function DashboardPage() {
return (
<div className="grid grid-cols-2 gap-4">
<ErrorBoundary fallback={<WidgetError name="Revenue" />}>
<RevenueChart />
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError name="Users" />}>
<UserStats />
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError name="Activity" />}>
<ActivityFeed />
</ErrorBoundary>
</div>
);
}
function WidgetError({ name }: { name: string }) {
return (
<div className="p-4 border border-red-200 rounded bg-red-50 text-red-700">
El widget {name} no se pudo cargar.
</div>
);
}Lo que esto demuestra:
- Interfaz alternativa con prop-render con acceso al objeto de error y una función de reinicio
- Prop
resetKeysque reinicia automáticamente el límite cuando cambian las dependencias (por ejemplo, parámetros de ruta) - Hook de reporte de errores (
onError) para Sentry o servicios similares - Límites granulares para que un widget roto no derriba la página completa
Inmersión Profunda
Cómo Funciona
- Los límites de error son componentes de clase que implementan
static getDerivedStateFromError()ocomponentDidCatch(). getDerivedStateFromErrorse ejecuta durante el renderizado para establecer el estado de error y desencadenar la interfaz alternativa.componentDidCatchse ejecuta después de la confirmación para efectos secundarios como el registro de errores.- Los límites de error capturan errores lanzados durante el renderizado, en métodos de ciclo de vida y en constructores de su subárbol de hijos.
- NO capturan errores en manejadores de eventos, código asincrónico, renderizado del lado del servidor o errores lanzados en el límite mismo.
Parámetros y Valores de Retorno
| Método de Ciclo de Vida | Cuándo Se Llama | Propósito |
|---|---|---|
getDerivedStateFromError(error) | Durante el renderizado | Devuelve nuevo estado para desencadenar interfaz alternativa |
componentDidCatch(error, info) | Después de la confirmación | Efectos secundarios: registro, reporte de errores |
info.componentStack | En componentDidCatch | Cadena que muestra la ruta del árbol de componentes |
Variaciones
Envoltorio de límite de error basado en hooks — usa un paquete o envoltorio delgado para ergonomía de hooks:
// Con react-error-boundary (librería popular)
import { ErrorBoundary } from "react-error-boundary";
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => queryClient.invalidateQueries()}
resetKeys={[routeKey]}
>
<AppContent />
</ErrorBoundary>
);
}
function ErrorFallback({
error,
resetErrorBoundary,
}: {
error: Error;
resetErrorBoundary: () => void;
}) {
return (
<div role="alert">
<p>Error: {error.message}</p>
<button onClick={resetErrorBoundary}>Reintentar</button>
</div>
);
}Límites anidados con diferentes granularidades:
// El límite a nivel de página captura todo
<ErrorBoundary fallback={<FullPageError />}>
<Layout>
{/* Los límites a nivel de sección aíslan fallos */}
<ErrorBoundary fallback={<SectionError />}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary fallback={<SectionError />}>
<MainContent />
</ErrorBoundary>
</Layout>
</ErrorBoundary>Notas de TypeScript
- Escribe el estado como
{ error: Error | null }en lugar de{ hasError: boolean }para preservar el objeto de error para la interfaz alternativa. - Usa
ErrorInfode React para el segundo parámetro decomponentDidCatch. - La propiedad
componentStackenErrorInfoes una cadena, no un objeto estructurado.
Trampas
-
Los errores en manejadores de eventos no se capturan — Los límites de error solo capturan errores durante el renderizado de React. Un
onClickque lanza no será capturado. Solución: Usa try/catch en manejadores de eventos y establece el estado de error local. -
Los errores asincrónico no se capturan — Las Promises que se rechacen dentro de
useEffecto manejadores de eventos no se capturan. Solución: Captura errores asincrónico y establece estado local o relanza durante el renderizado (por ejemplo, almacena en estado y lanza en renderizado). -
Sin límites de error basados en hooks — React no proporciona un equivalente de hook para
getDerivedStateFromError. Solución: Usa el patrón de componente de clase o la libreríareact-error-boundary. -
El límite de error mismo lanzando — Si el método render del límite de error lanza, se propaga al límite padre más cercano. Solución: Mantén los métodos render del límite de error simples. Nunca lances en la interfaz alternativa.
-
Recuperación sin desmontaje — Restablecer
hasErrora false sin remontar hijos puede dejarlos en estado roto. Solución: Usa una propkeyen el envoltorio de hijos para forzar remontaje en el restablecimiento.
Alternativas
| Enfoque | Compensación |
|---|---|
| Límites de error | Captura errores de renderizado; requiere componente de clase |
react-error-boundary | API amigable con hooks, resetKeys; dependencia extra |
| Try/catch en manejadores de eventos | Manual; no captura errores de renderizado |
| Suspense con manejo de errores | Maneja errores de carga asincrónico via promesas lanzadas |
Global window.onerror | Captura errores no capturados; sin recuperación específica de React |
Ejemplo del Mundo Real
De una aplicación SaaS de Next.js 15 / React 19 en producción (SystemsArchitect.io).
// Ejemplo de producción: Next.js error.tsx con registro, reintentos y recarga completa de página alternativa
// Archivo: app/error.tsx
'use client';
import { useEffect } from 'react';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Registra al servicio de seguimiento de errores externo
console.error('Application error:', error);
// reportToSentry(error);
}, [error]);
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-center space-y-4">
<h2 className="text-2xl font-bold">Algo salió mal</h2>
{error.digest && (
<p className="text-sm text-gray-500">ID del Error: {error.digest}</p>
)}
<div className="flex gap-3 justify-center">
<button
onClick={reset}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Intentar de nuevo
</button>
<button
onClick={() => window.location.href = '/'}
className="px-4 py-2 border rounded hover:bg-gray-50"
>
Ir a inicio
</button>
</div>
</div>
</div>
);
}Lo que esto demuestra en producción:
error.tsxes una convención de Next.js App Router que automáticamente envuelve la página en un límite de error. No necesitas escribir un límite de error basado en clase por ti mismo.useEffectregistra el error en el montaje. Este es el lugar correcto para enviar errores a Sentry o un servicio similar. No registres en el cuerpo del renderizado porque se ejecuta durante la fase de renderizado de React.reset()re-renderiza el segmento de ruta, lo que reintenta el componente fallido. Esto funciona bien para errores transitorios como fallos de red o condiciones de carrera.error.digestes un identificador de error hasheado que Next.js genera para errores del lado del servidor. Es seguro mostrar a los usuarios (no expone trazas de pila ni detalles sensibles). En desarrollo, se muestra el error completo en su lugar.- El botón "Ir a inicio" usa
window.location.hrefen lugar derouter.push()como una opción deliberada. Si el error corrompió el árbol de React o el estado del enrutador del lado del cliente,router.push()podría fallar o renderizar la misma página rota. Una recarga completa de página a través dewindow.location.hrefgarantiza un inicio limpio. - Este
error.tsxdebe ser un Client Component ('use client'). Los límites de error requieren React del lado del cliente para capturar y recuperarse de errores.
FAQs
¿Qué es un límite de error y qué errores captura?
- Un límite de error es un componente de clase que captura errores durante el renderizado, métodos de ciclo de vida y constructores de su subárbol de hijos.
- Muestra una interfaz alternativa en lugar de romper la aplicación completa.
- NO captura errores en manejadores de eventos, código asincrónico, SSR, o errores en el límite mismo.
¿Por qué los límites de error son componentes de clase y no hooks?
- React no proporciona un equivalente de hook para
getDerivedStateFromErrorocomponentDidCatch. - Estos métodos de ciclo de vida solo están disponibles en componentes de clase.
- Usa la librería
react-error-boundarysi prefieres una API de envoltorio amigable con hooks.
¿Cuál es la diferencia entre getDerivedStateFromError y componentDidCatch?
getDerivedStateFromErrorse ejecuta durante la fase de renderizado para actualizar el estado y desencadenar la interfaz alternativa. Debe ser una función pura.componentDidCatchse ejecuta después de la fase de confirmación para efectos secundarios como registrar errores a Sentry.- Usa ambos: uno para mostrar la interfaz alternativa, uno para reportar.
¿Cómo funciona resetKeys para la recuperación automática de errores?
<ErrorBoundary
resetKeys={[userId]}
renderFallback={({ error, reset }) => (
<div>
<p>{error.message}</p>
<button onClick={reset}>Reintentar</button>
</div>
)}
>
<ProfileContent userId={userId} />
</ErrorBoundary>- Cuando
resetKeyscambia (por ejemplo,userIdcambia), el límite automáticamente se restablece e intenta renderizar nuevamente. - Esto funciona bien con cambios de parámetros de ruta para recuperarse de errores obsoletos.
Trampa: ¿Por qué los límites de error no capturan errores en manejadores de eventos?
- Los límites de error solo capturan errores lanzados durante el ciclo de vida de renderizado de React.
- Un manejador
onClickque lanza no interrumpe el renderizado; lanza en el bucle de eventos del navegador. - Solución: usa try/catch dentro de manejadores de eventos y establece el estado de error local.
Trampa: ¿Qué sucede si el método render del límite de error lanza?
- El error se propaga al límite de error padre más cercano.
- Si no existe un límite padre, la aplicación completa se rompe.
- Solución: mantén los métodos render del límite de error y la interfaz alternativa simples. Nunca lances en la interfaz alternativa.
¿Cómo tipas un componente límite de error en TypeScript?
interface ErrorBoundaryState {
error: Error | null;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
state: ErrorBoundaryState = { error: null };
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { error };
}
}- Escribe el estado como
{ error: Error | null }para preservar el objeto de error para la interfaz alternativa. - Usa
ErrorInfode React para el segundo parámetro decomponentDidCatch.
¿Qué es error.digest en los límites de error de Next.js?
error.digestes un identificador de error hasheado generado por Next.js para errores del lado del servidor.- Es seguro mostrar a los usuarios porque no expone trazas de pila ni detalles sensibles.
- En desarrollo, el error completo se muestra en su lugar.
¿Por qué el botón "Ir a inicio" de Next.js error.tsx usa window.location.href en lugar de router.push()?
- Si el error corrompió el árbol de React o el estado del enrutador del lado del cliente,
router.push()podría fallar o renderizar la misma página rota. window.location.hrefdesencadena una recarga completa de página, garantizando un inicio limpio.- Este es un patrón deliberado de seguridad en producción.
¿Cómo deberías estructurar los límites de error para aislamiento de errores granular?
- Coloca un límite a nivel de página para capturar cualquier error no capturado como último recurso.
- Coloca límites a nivel de sección alrededor de widgets o características individuales para que un fallo no derriba la página completa.
- Cada límite puede mostrar su propia interfaz alternativa localizada.
¿Cómo restableces un límite de error sin dejar los hijos en estado roto?
- Simplemente restablecer
hasErrorafalsere-renderiza los mismos hijos, que pueden seguir siendo rotos. - Solución: usa una prop
keyen el envoltorio de hijos para forzar desmontaje y remontaje completo en el restablecimiento. - Esto asegura que los hijos comiencen frescos con estado limpio.
Relacionado
- Suspense — Límites de estado de carga que se emparejan con límites de error
- Máquinas de Estado — Modela explícitamente estados de error en transiciones de estado
- Composición — Los límites de error usan el patrón de composición con
children