Как создать компонент обработчика ошибок с ErrorBoundary и componentDidCatch

#javascript #reactjs

#javascript #reactjs

Вопрос:

Я обнаружил, что пытаюсь создать обработчик ошибок с ErrorBoundary, однако через пару дней и много терпения мне удалось зафиксировать ошибку и сохранить ее в базе данных, вызвав API в серверной части.

ПРИМЕЧАНИЕ: если у вас есть лучшее решение, пожалуйста, прокомментируйте этот пост.

Первое: создайте свой компонент ErrorBoundary, как указано в документации ReactJS, за исключением пары изменений, которые добавляют

Как только вы вводите componentDidCatch, ошибка Boundary отправляется непосредственно в рендеринг, он не проходит через другой жизненный цикл React, затем мы создали другой промежуточный компонент, в котором можно вызывать API и иметь возможность спокойно использовать жизненные циклы, но мы добавили метод обратного вызова, чтобы уведомить ErrorBoundary о том, что ошибка уже была уведомлена, и перенаправить представление home или любое другое ваше основное представление.

первое: мой компонент Errorboundary:

  /**
 * @desc Dependencias
 */
import React, { Component, Fragment } from 'react';

/**
 * @desc Acciones
 */
import { saveError } from '../../actions/ErrorsActions';

/**
 * @desc Componentes
 */
import Modal from '../Modal/ModalContainer';
import ErrorMessage from './errorMesage';

class ErrorBoundary extends Component {

  /**
   * @desc Constructor del componente.
   * 
   * @param { Object } props 
   * 
   * @return { Void }
   */
  constructor(props) {

    super( props );

    // Estado incial.
    this.state = { 

      // Se encontro un error.
      foundError: {
        display : false,
        message: null
      }
    };
  }

  /**
   * @desc Ciclo de vida: Comienzo del montado del componente.
   * 
   * @return { void }
   */
  componentDidMount(){

    // Global catch of unhandled Promise rejections:
    global.onunhandledrejection = error => {  

      // Warning: when running in "remote debug" mode (JS environment is Chrome browser),
      // this handler is called a second time by Bluebird with a custom "dom-event".
      // We need to filter this case out:
      if (error instanceof Error) {

        // Alias
        let { foundError } = this.state;

        // Indice del split al stack.
        let index = error.stack.indexOf(".");

        // Asignamos el stack
        foundError.display = true;
        foundError.message = error.stack.substring( 0, index )
                                        .trim()
                                        .replace(/(rn|n|r)/gm, "");

        // Actualizamos el estado con el error.
        this.setState({ foundError })        

      }
    };
  }

  /**
   * @desc Detecta los errores no manipulados de la aplicación.
   * 
   * @param { Object } error 
   * @param { Object } info 
   * 
   * @return { Void }
   */
  componentDidCatch( error, info) {

    // Alias
    let { foundError } = this.state;

    // Indice del split al stack.
    let index = info.componentStack.indexOf("(");

    // Mensaje del error
    let message = error.message   " - "   info.componentStack.substring( 0, index ).trim();

    // Asignamos el stack
    foundError.display = true;
    foundError.message = message;

    // Actualizamos el estado con el error.
    this.setState({ foundError })

  }

  /**
   * @desc 
   * 
   * @return { void }
   */
  async closeModal(){

    // `enter code here`Alias
    let { foundError } = this.state;

    // Reiniciamos el estado del error.
    foundError.display = false;
    foundError.message = null;

    // Actualizamos el estado
    await this.setStateAsync({ foundError });

  }

  /**
   * @desc Renderiza el componente.
   * 
   * @return { * }
   */
  render() {

    // Alias
    let { language, children } = this.props;
    let { foundError } = this.state;

    // Validamos si se debe mostrar el modal de crash.
    if( !foundError.display )
      return children;
    else
      // You can render any custom fallback UI
      return ( <ErrorMessage { ...this.props }
                             onClose={ () => this.closeModal() }
                             stack={ foundError.stack } 
                             message={ foundError.message } /> );
  }
}

export default ErrorBoundary;
  

При рендеринге мы проверяем, была ли ошибка, если да, мы вызываем наш компонент как компонент ошибки, если нет, мы возвращаем дочерние элементы.

в компоненте ErrorMessage:

 /**
 * @desc Dependencias
 */
import React, { Component, Fragment } from 'react';

/**
 * @desc Lenguaje
 */
import Lenguaje from '../../lenguage/es-ar.json';

/**
 * @desc Acciones
 */
import { saveError } from '../../actions/ErrorsActions';

/**
 * @desc Componentes
 */
import Modal from '../Modal/ModalContainer';

class ErrorMeesage extends Component {


  /**
   * @desc Constructor del componente.
   * 
   * @param { Object } props 
   * 
   * @return { Void }
   */
  constructor(props) {

    super( props );

    this.state = {

      // Modal de crash.
      modalCrash: {
        display: true,
        type: 'crash',
        content: {
          title: '',
          body: '',
        }
      }

    }

  }

  componentDidMount(){

    // Lanzamos el almacenamiento del stack.
    this.saveAudit();

  }

  /**
    * @desc Permite el acceso asincronico al setState de react.
    *
    * @param { Object } state
    * 
    * @return { Promise }
  */
  setStateAsync( state ){
   return new Promise( resolve => this.setState( state,  () => {
     resolve()
    }))
  }

  /**
   * @desc Envia para almacenar el stack de errores.
   * 
   * @return { Promise<void> }
   */
  async saveAudit(){

    // Alias
    let { history, country, message } = this.props;

    // Datos.
    let data = {
      type: 'WEB',
      subtype: 'ERROR',
      details: message
    };

    // Solicitamos el guardado del error.
    let response =  saveError( country, data );

    // Esperamos la solicitud se cumpla. 
    response.payload = await response.payload;

    // Datos del modal.
    let title       = Lenguaje amp;amp; Lenguaje.WAS_AN_ERROR,
        body        = Lenguaje.GENERAL_ERROR_CODE.replace('{0}', response.payload.data ),
        callback    = async () => {

          await this.hideModalCrash();

          // Redirección.
          history.push('/home');

        };

    // Mostramos el modal de crash.
    await this.showModalCrash( title, body, callback ); 

  }

  /**
  * Muestra el modal de error.
  * 
  * @param { String } title
  * @param { String } body
  * 
  * @return { Promise<Boolean> }
  */
  async showModalCrash( title = '', body = '', callback = null){

  // Alias
  let { modalCrash } = this.state;

  try{

    // Asignamos los valores al modal.
    modalCrash.display = true;
    modalCrash.content.title = title; 
    modalCrash.content.body = body;
    modalCrash.callback = typeof callback === "function" amp;amp; callback;

    // Actualizamos el estado interno.
    this.setState({ modalCrash });

    return true;

  }catch( error ){

    return false;

  }

  }

  /**
  * Oculta el modal de error.
  * 
  * @return { Promise<Boolean> }
  */
  async hideModalCrash(){

    // Alias
    let { modalCrash } = this.state;

    try{

      // Limpiamos el modal de eliminación.
      modalCrash.display = false;
      modalCrash.content.title = '';
      modalCrash.content.body = '';

      // Actualizamos el estado.
      await this.setStateAsync({ modalCrash });

      // El modal fue ocultado.
      await this.props.onClose();

      return true;

    }catch( error ){

      return false;

    }

  }

  /**
   * @desc Renderiza el componente.
   * 
   * @return { * }
   */
  render() {

    // Alias
    let { modalCrash } = this.state;
    let { language } = this.props;

    return(<Modal  type="crash"
                    isWrapper 
                    dataModal={ modalCrash } 
                    language={ language }
                    handleClick={ () => typeof modalCrash.callback === "function" amp;amp; modalCrash.callback() } />);

  }

}
export default ErrorMessage;
  

в компоненте ErrorMessage мы вызываем API для сохранения ошибки, показываем модал для уведомления пользователя, а затем в HideCrashModal у нас есть метод, который уведомляет ErrorBoundary о том, что ошибка была уведомлена: await this.props.OnClose ();

пример того, как реализовать этот компонент:

   <Route path="/home"    render= { props => 
            <ErrorBoundary { ...this.props }> 
              <Welcome history={ props.history } dispatch={ props.dispatch } />  
            </ErrorBoundary>
            } />
  

Теперь componentDidCatch не может фиксировать ошибки в асинхронном коде, однако мы заменяем обещания на обещания BlueBird, и мы можем использовать метод фиксации этих ошибок в нашем ErrorBoundary, чтобы у нас была централизованная точка ошибки, поэтому мы добавляем global.onunhandlerrejectionв жизненный цикл componentDidMount таким образом, если обработчик ошибок BlueBird обнаружит ошибку в каком-либо обещании, он примет ее в нашем ErrorBoundary.

Комментарии:

1. Вы просите отзыв?

2. да, а также, если кто-то не знает, как использовать componentDidCatch и отлавливать ошибки асинхронного кода, я покажу вам, как я мог бы это решить

3. Добро пожаловать в Stack Overflow. Если вы разделите это на вопрос и ответ, вы можете «самостоятельно ответить» на него и получить оценку. Прямо сейчас, как запрос на проверку кода, это, вероятно, будет расценено как не по теме. В сети Stack Exchange есть отдельный сайт для обзоров кода, codereview.stackexchange.com но вы захотите прочитать их обзор, прежде чем использовать этот сайт.