Saltar al contenido

Lección 14 de 15 · Lectura y práctica

Error boundaries y manejo de errores en React

CONCEPTO CLAVE: Los Error Boundaries son componentes React especiales que capturan errores de JavaScript en cualquier parte de su árbol de componentes, los registran y muestran una UI alternativa en lugar de bloquear toda la.

Del concepto a la práctica

Taller de esta lección

Explicación

Un error boundary captura errores de renderizado en su subárbol y muestra una alternativa. No sustituye el manejo de errores de eventos, promesas o validaciones; esos flujos deben tratarse donde ocurren.

Ejemplo resuelto

jsx
class Limite extends React.Component {
  state = { fallo: false };
  static getDerivedStateFromError() { return { fallo: true }; }
  render() { return this.state.fallo ? <p>Panel no disponible</p> : this.props.children; }
}
<Limite><PanelDisponibilidad /></Limite>

Si PanelDisponibilidad falla al renderizar, el resto del árbol puede continuar.

Pruébalo

  1. Envuelve solo el panel frágil.

  2. Fuerza un error local controlado.

  3. Comprueba que el encabezado sigue visible.

Error frecuente: esperar que capture un rechazo de promesa; corrígelo con try/catch o estado de error en la operación asíncrona.

Comprueba tu resultado

Ejercicio: crea un panel que falle cuando datos sea nulo. El límite debe mostrar “Panel no disponible”, mientras una sección hermana conserva el texto “Sesión activa”.

Una vez capturado un error, el estado failed/fallo de este ejemplo permanece activo. Quitar el lanzamiento del hijo no reinicia el límite automáticamente: remóntalo con una key diferente o implementa un reinicio explícito tras corregir el dato. Un error dentro del propio límite o su fallback necesita un límite antecesor.

Tres filas separan error durante render, capturado por Boundary con interfaz alternativa, error de clic manejado con try/catch y promesa fetchData rechazada manejada con catch.
CursaloMapa de práctica
Qué captura una Error Boundary

Una Error Boundary situada sobre el componente captura errores que se lanzan durante su render y muestra una interfaz alternativa. Un handler de clic y una promesa ordinaria como fetchData requieren manejo explícito en su propio código. Un import de lazy rechazado constituye otro caso: React lanza ese error durante el render al boundary más cercano.

Ver la infografía en tamaño completo ↗
Fuentes para profundizar
CONCEPTO CLAVE: Los Error Boundaries son componentes React especiales que capturan errores de JavaScript en cualquier parte de su árbol de componentes, los registran y muestran una UI alternativa en lugar de bloquear toda la aplicación. Son el mecanismo principal para el manejo de errores en aplicaciones React.

¿Por qué necesitamos Error Boundaries?

En las aplicaciones React modernas, los errores en un componente pueden propagarse y destruir toda la interfaz de usuario. Imagina que un pequeño componente de botón falla: sin Error Boundaries, toda tu aplicación se volvería inoperable. Los Error Boundaries actúan como "contenedores de seguridad" que aíslan los errores y mantienen funcionando el resto de la aplicación.

📌 Los Error Boundaries fueron introducidos en React 16 como parte del concepto de "Error Boundaries" o "límites de error". Son fundamentales para crear aplicaciones React robustas y profesionales.

¿Qué pueden y qué no pueden capturar?

Es crucial entender las limitaciones de los Error Boundaries:

Pueden capturarNo pueden capturar
Errores en renderizaciónEventos (onClick, onChange, etc.)
Métodos del ciclo de vidaCódigo asíncrono (setTimeout, Promises)
Constructores de componentesErrores fuera del árbol de componentes
Errores en hooks personalizadosEl propio Error Boundary

Implementación de un Error Boundary

Para crear un Error Boundary, necesitas una clase de componente React que implemente uno o ambos de estos métodos estáticos:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }

  static getDerivedStateFromError(error) {
    // Actualiza el estado para mostrar la UI de respaldo
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // Puedes registrar el error en un servicio externo
    console.error('Error capturado:', error, errorInfo);
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div className="error-fallback">
          <h2>Algo salió mal</h2>
          <details>
            <summary>Ver detalles del error</summary>
            <p>{this.state.error && this.state.error.toString()}</p>
          </details>
        </div>
      );
    }
    return this.props.children;
  }
}
💡 Tip: getDerivedStateFromError() se usa para renderizar la UI alternativa, mientras que componentDidCatch() es ideal para logging y reporting de errores a servicios externos como Sentry, Bugsnag o tu propio backend.

Uso práctico de Error Boundaries

Ahora veamos cómo aplicar el Error Boundary en tu aplicación:

import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import Header from './Header';
import Content from './Content';
import Sidebar from './Sidebar';

function App() {
  return (
    <div className="app">
      <Header />
      
      <ErrorBoundary>
        <Sidebar />
      </ErrorBoundary>
      
      <ErrorBoundary>
        <Content />
      </ErrorBoundary>
    
  );
}

// También puedes crear múltiples Error Boundaries específicos
function Dashboard() {
  return (
    <ErrorBoundary fallback={
⚠️ Advertencia: No envuelvas toda tu aplicación en un solo Error Boundary. Esto haría que al fallar cualquier componente, toda la aplicación muestre la misma interfaz de error. Usa múltiples Error Boundaries para aislar componentes específicos y mantener funcionando el resto.

Manejo de errores en código asíncrono

Los Error Boundaries NO capturan errores en manejadores de eventos, callbacks asíncronos o Promises. Para estos casos, necesitas usar try-catch tradicional:

function UserProfile({ userId }) {
  const [user, setUser] = React.useState(null);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    async function fetchUser() {
      try {
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error('Usuario no encontrado');
        const data = await response.json();
        setUser(data);
      } catch (err) {
        setError(err.message);
      }
    }
    fetchUser();
  }, [userId]);

  if (error) return <ErrorMessage message={error} />;
  if (!user) return <LoadingSpinner />;
  return <Profile user={user} />;
}

Patrones avanzados de Error Boundaries

1. Error Boundary con fallback personalizado

function ErrorBoundaryWithFallback({ fallback, children }) {
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    const errorHandler = (event) => {
      setError(event.error);
      event.preventDefault();
    };

    window.addEventListener('error', errorHandler);
    return () => window.removeEventListener('error', errorHandler);
  }, []);

  if (error) {
    return fallback({ error, reset: () => setError(null) });
  }

  return children;
}

// Uso:
<ErrorBoundaryWithFallback
  fallback={({ error, reset }) => (
    <div>
      <p>Error: {error.message}</p>
      <button onClick={reset}>Reintentar</button>
    </div>
  )}
>
  <ComponenteInestable />
</ErrorBoundaryWithFallback>
📌 Este patrón es útil cuando necesitas pasar callbacks adicionales al fallback, como una función para reintentar la operación o resetear el estado del componente.

2. Error Boundary con niveles de recuperación

class RecoveryErrorBoundary extends React.Component {
  state = { retryCount: 0 };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  handleRetry = () => {
    this.setState(prev => ({ 
      hasError: false, 
      retryCount: prev.retryCount + 1 
    }));
  };

  render() {
    if (this.state.hasError) {
      return (
        <div className="recovery-ui">
          <h3>⚠️ Componente temporalmente no disponible</h3>
          <p>Intentos: {this.state.retryCount}</p>
          {this.state.retryCount < 3 ? (
            <button onClick={this.handleRetry}>
              Reintentar automáticamente
            </button>
          ) : (
            <p>Por favor, recarga la página</p>
          )}
        </div>
      );
    }
    return this.props.children;
  }
}
💡 Tip profesional: Implementa niveles de recuperación progresiva. Si un componente falla, intenta recuperación automática. Si falla repetidamente, muestra opciones más drásticas como recargar la sección o la página completa.

Estrategias de monitoreo y logging

Para aplicaciones en producción, es esencial registrar los errores capturados:

// Servicio de logging centralizado
const errorLoggingService = {
  log(error, errorInfo) {
    // Enviar a tu backend
    fetch('/api/errors', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        message: error.message,
        stack: error.stack,
        componentStack: errorInfo.componentStack,
        timestamp: new Date().toISOString(),
        userAgent: navigator.userAgent,
        url: window.location.href,
      }),
    });
  },

  // Integración con servicios externos
  logToSentry(error, errorInfo) {
    // Sentry.captureException(error, { extra: errorInfo });
  },

  logToBugsnag(error, errorInfo) {
    // Bugsnag.notify(error, report => {
    //   report.metadata = { errorInfo };
    // });
  },
};

class ProductionErrorBoundary extends React.Component {
  componentDidCatch(error, errorInfo) {
    errorLoggingService.log(error, errorInfo);
    
    if (process.env.NODE_ENV === 'production') {
      errorLoggingService.logToSentry(error, errorInfo);
    }
  }

  render() {
    return this.props.children;
  }
}
⚠️ Importante: Nunca expongas información sensible del error en la UI de producción. Los stack traces detallados deben enviarse solo a tus servicios de logging, nunca al usuario final.

Integración con React Router

Un patrón común es combinar Error Boundaries con React Router para manejar errores de navegación:

import { BrowserRouter, Routes, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <ErrorBoundary>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route 
            path="/dashboard" 
            element={
              <ErrorBoundary fallback={
Ver más: Ejemplo completo de Error Boundary reutilizable
import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
    };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    this.props.onError?.(error, errorInfo);
  }

  resetError = () => {
    this.setState({ hasError: false, error: null, errorInfo: null });
  };

  render() {
    if (this.state.hasError) {
      if (this.props.fallback) {
        return this.props.fallback({
          error: this.state.error,
          errorInfo: this.state.errorInfo,
          resetError: this.resetError,
        });
      }

      return (
        <div className="error-boundary" role="alert">
          <h2>Error</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={this.resetError}>Reintentar</button>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

// Componente wrapper para uso con hooks
export function withErrorBoundary(
  Component,
  fallback
) {
  return function WrappedComponent(props) {
    return (
      <ErrorBoundary fallback={fallback}>
        <Component {...props} />
      </ErrorBoundary>
    );
  };
}

Mejores prácticas

  1. Aísla los errores: Coloca Error Boundaries en niveles estratégicos de tu aplicación, no envolver todo.
  2. Proporciona contexto: Muestra mensajes útiles al usuario sin exponer detalles técnicos sensibles.
  3. Implementa logging: Envía los errores a un servicio de monitoreo para poder depurarlos.
  4. Ofrece recuperación: Incluye opciones para reintentar o navegar a un estado seguro.
  5. Prueba tus Error Boundaries: Simula errores intencionalmente para verificar que funcionan correctamente.
Los Error Boundaries son como los airbags de tu aplicación: esperas no necesitarlos, pero cuando ocurre un error, son la diferencia entre una recuperación suave y un fracaso total.
📌 Recuerda: los Error Boundaries no pueden manejar todos los tipos de errores. Para eventos, Promises y código asíncrono, siempre usa try-catch tradicional junto con el manejo de estado de React.

Ejercicio práctico

Implementa un Error Boundary que:

  • Muestre un mensaje amigable cuando un componente falla
  • Tenga un botón "Reintentar" que recupere el componente
  • Registre los errores en la consola
  • Limit los reintentos a 3 intentos antes de mostrar un mensaje de "contactar soporte"
🧠 Quiz: Error Boundaries

¿Cuál de los siguientes errores SÍ puede capturar un Error Boundary?

  • A) Errores en manejadores onClick
  • B) Errores en Promises con await
  • C) Errores durante el renderizado de un componente
  • D) Errores en setTimeout
✅ Respuesta correcta: C) Errores durante el renderizado. Los Error Boundaries capturan errores en el render, métodos del ciclo de vida y constructores. NO capturan errores en manejadores de eventos, código asíncrono (setTimeout, Promises) ni fuera del árbol de componentes.

Conclusión

Los Error Boundaries son una herramienta esencial para crear aplicaciones React robustas y profesionales. Combinados con manejo de errores tradicional en código asíncrono y sistemas de logging externos, te permiten crear experiencias de usuario resilientes donde incluso cuando algo falla, la aplicación puede recuperarse elegantemente. Practica implementando Error Boundaries en diferentes niveles de tu aplicación y siempre prueba sus comportamientos bajo condiciones de error.

Sigue con React Intermedio: Dominando Componentes y Patrones Avanzados

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.