Entendiendo los Error Boundaries en ReactJS

Entendiendo los Error Boundaries en ReactJS

En versiones anteriores de ReactJS, cuando se producía un error de javascript dentro de un componente, este mismo afectaba su funcionamiento interno lo cual rompía la interfaz de usuario y para aquel entonces ReactJS no proveía de una forma de evitarlo, pero a partir de la versión 16 ahora es posible capturar cualquier error dentro de un componente y desmontarlo del DOM, ayudando a que la interfaz siga funcionando correctamente.

Qué son los Error Boundaries?

Los Error Boundaries son componentes los cuales tienen como función capturar errores de javascript en cualquier lugar de los nodos hijos donde se produzca el error, se puede obtener información de la traza y también mostrar una interfaz alternativa en lugar del componente que dejó de funcionar.

Específicamente, los error boundaries pueden capturar errores durante:

  • Constructores
  • Ciclo de vida de los componentes (lifecycle methods)
  • Renderización de un componente

Y es importante mencionar que los error boundaries no capturan errores para:

  • Event handlers
  • Código asíncrono
  • Renderización desde el servidor (SSR)
  • Errores lanzados dentro del componente de error boundary mismo

En términos generales, digamos que los error boundaries son como los bloques catch {} en javascript, pero para componentes exclusivamente.

Cómo funcionan los Error Boundaries?

Crear un componente de tipo error boundary es sumamente sencillo, lo primero a tomar en cuenta es que se debe de construir un componente de tipo clase o Class Component e igualmente importante es que se debe de implementar el método componentDidCatch el cual recibe dos parámetros que son error y errorInfo.

Explicaré los error boundaries creando 2 componentes muy básicos, el primero de ellos será un contenedor de una lista y el segundo será una tarjeta o card el cual utilizaré para renderizar una lista de elementos en el contenedor.

Componente Card (card.tsx)


import React from 'react';

export interface CardComponentDefinition {
    title: string;
    date: Date;
    mod: 'contento' | 'tranquilo',
    error?: boolean;
};

export const CardComponent = (props: CardComponentDefinition): JSX.Element => {
    const { title, date, mod, error } = props;

    if (error) throw new Error('Ops.. algo anda mal');

    return (
        <div>
            <h2>{title}</h2>
            <div>{date.toLocaleDateString()}</div>
            <div>{mod}</div>
        </div>
    );
};

Componente Lista (list.tsx)


import React from 'react';
import { CardComponent, CardComponentDefinition } from './card';

export const ListComponent = (): JSX.Element => {
    const items: CardComponentDefinition[] = [
        {
            title: 'Titulo 1',
            date: new Date('01-01-2020'),
            mod: 'contento'
        },
        {
            title: 'Titulo 2',
            date: new Date('01-06-2020'),
            mod: 'tranquilo',
            error: true // forzando a que el segundo componente de card falle
        },
        {
            title: 'Titulo 3',
            date: new Date('01-12-2020'),
            mod: 'contento'
        }
    ];

    return (
        <div>
            {items.map((item, index) => {
                return <CardComponent key={index} {...item} />
            })}
        </div>
    );
};

Ejemplo #1

Ejecutando la aplicación sin error boundaries y haciendo que uno de los componentes falle intencionalmente.

Si notan la definición de la interface que he utilizado para card.tsx y para efectos de prueba he introducido el atributo error que es una propiedad de tipo boolean, que al recibirse un valor verdadero, lanzará una excepción en el componente y que de hecho romperá toda la aplicación, enviando un error como el siguiente:

Ejemplo #2

Esta vez crearemos el componente error.tsx el cual será nuestro Error Boundary y la intención es envolver el componente CardComponent con nuestro nuevo componente.

Componente error boundary (error.tsx)


import React from 'react';

interface ErrorBoundaryState {
    error?: Error;
    errorInfo?: React.ErrorInfo;
}

export default class ErrorBoundaryComponent extends React.Component<{}, ErrorBoundaryState> {
    constructor(props: any) {
        super(props);
        this.state = { error: undefined, errorInfo: undefined };
    }

    componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
        this.setState({
            error: error,
            errorInfo: errorInfo,
        });

        // el error se puede enviar a cualquier servicio externo

    }

    render() {
        if (this.state.error) {
            return <h2>{this.state.error?.message}</h2>
        }

        return this.props.children;
    }
}

Y la forma de utilizarlo entonces es la siguiente, nótese que solamente estoy modificando el componente list.tsx:


    return (
        <div>
            {items.map((item, index) => {
                return  (
                    <ErrorBoundaryComponent key={index}>
                        <CardComponent {...item} />
                    </ErrorBoundaryComponent>
                );
            })}
        </div>
    );

y el resultado final en lugar de tener un error sería algo como esto:

Y así es como funcionan los Error Boundaries en ReactJS, incluso se pueden utilizar en React Native también y por supuesto que se puede utilizar más de una vez en un mismo componente para evitar errores en diferentes secciones.

Espero que este artículo les sea de ayuda y les ayude a entender como controlar mejor sus errores en ReactJS, gracias por leer y bienvenidos sus comentarios.

P.D. Una última cosa, quizás notarán que el error sigue apareciendo en su navegado y es totalmente normal, ya que están en modo de desarrollo, una vez que se pone en marcha el build para producción, el mensaje de error deja de aparecer y dará paso al componente o códgo que hayan destinado para tal fin.

Happy Coding!


Photo by Sarah Kilian on Unsplash

Jack Fiallos

Jack Fiallos