O tratamento elegante de erros é um aspecto essencial de um software bem projetado. Também é complicado. Este artigo oferece uma visão geral do tratamento de erros em aplicativos React e como usar os limites de erro do React para lidar com erros de tempo de renderização.

Tipos de erro de reação

Podemos dividir os erros do aplicativo React amplamente em dois tipos e o tratamento de erros em dois aspectos.

Os dois tipos de erro React:

  • Erros de JavaScript: são erros convencionais de JavaScript que ocorrem na parte do código de um componente.
  • Erros de renderização: são erros gerados pelo mecanismo de renderização, emergentes da marcação.

Observe que a natureza da UI JavaScript torna o tratamento de erros complicado. Além dos erros típicos de tempo de execução, existem erros que surgem do “desenho” dos componentes da tela. Estamos distinguindo esses dois tipos de erros aqui como “erros de JavaScript” e “erros de renderização”.

Erros de JavaScript ocorrem no código e podem ser tratados com blocos try/catch padrão, enquanto erros de renderização ocorrem nos modelos de visualização e são tratados pelos limites de erro do React. Podemos pensar nos limites de erro como blocos try/catch expressos na marcação do modelo.

Existem dois aspectos do tratamento de erros em ambos os casos:

  • Exibindo informações para o usuário
  • Fornecendo informações ao desenvolvedor

Em geral, você deseja mostrar apenas a quantidade mínima de informações de erro aos usuários e revelar a quantidade máxima de informações aos desenvolvedores, tanto no momento do desenvolvimento quanto em outros momentos, como teste e produção. Uma boa regra para o tratamento de erros é “falhar suavemente” para os usuários e “falhar fortemente” para os desenvolvedores.

Limites de erro de reação

O tipo de tratamento de erros mais distinto e específico do React é conhecido como limites de erro. Este recurso foi introduzido no React 16 e permite definir componentes que atuam como mecanismos de detecção de erros para a árvore de componentes abaixo deles.

Veremos um exemplo em breve, mas um erro de renderização só se torna aparente quando o mecanismo React está interpretando a marcação. Isso pode acontecer em qualquer lugar na hierarquia de componentes que entram em uma interface.

A ideia central é construir um widget que renderize condicionalmente uma visualização dependendo de seu estado de erro. O React fornece dois métodos de ciclo de vida que um componente pode implementar para determinar se ocorreu um erro de renderização em sua árvore filho e responder de acordo.

Esses dois métodos são componentDidCatch() e getDerivedStateFromError(), que é estático. Em ambos os casos, o objetivo principal é atualizar o estado do componente para que ele possa responder aos erros que chegam do mecanismo React.

componenteDidCatch()

O componentDidCatch() O método é normal e pode atualizar o estado do componente, bem como executar ações como fazer uma chamada de serviço para um back-end de relatório de erros. A Listagem 1 nos mostra usando esse método.

Listagem 1. componenteDidCatch


componentDidCatch(error, errorInfo) {
    errorService.report(errorInfo);
    this.setState({ error: error, errorInfo: errorInfo })
  }

Na Listagem 1, a função primária garante que o estado do componente entenda que ocorreu um erro e transmita as informações sobre esse erro. Observe que isso componentDidCatch() tem acesso ao tempo de execução.

getDerivedStateFromError()

Porque getDerivedFromError() é estático, não tem acesso ao estado do componente. Seu único propósito é receber um objeto de erro e depois retornar um objeto que será adicionado ao estado do componente. Por exemplo, consulte a Listagem 2.

Listagem 2. getDerivedStateFromError()


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

A Listagem 2 retorna um objeto com um sinalizador de erro que pode então ser usado pelo componente em sua renderização. Poderíamos lidar com necessidades mais elaboradas construindo objetos de erro mais complexos.

Renderização baseada em erro

Agora, vamos dar uma olhada na renderização do nosso componente de tratamento de erros, conforme visto na Listagem 3.

Listagem 3. Renderização ErrorBoundary


render() {
  if (this.state.error && this.state.errorInfo) {
    return (
      <div>
        <p>Caught an Error: {this.state.error.toString()}</p>
        <div>
          {this.state.errorInfo.componentStack}
        </div>
      </div>
    );
  } else {
    return this.props.children;
  }
}

Na Listagem 3 você pode ver que a ação padrão do componente é renderizar seus filhos. Ou seja, é um componente de passagem simples. Se um estado de erro for encontrado (como na Listagem 1 ou na Listagem 2), a visualização alternativa será renderizada. Embora o comportamento padrão seja renderizar a interface, um estado de erro invoca um caminho alternativo, algo como um bloco catch.

Usando o componente ErrorBoundary

Agora você viu os elementos essenciais de um componente de tratamento de erros no React. A utilização do componente é muito simples, conforme mostrado na Listagem 4.

Listagem 4. Exemplo do componente ErrorBoundary


<Parent>
  <ErrorBoundary>
    <Child><Child/>
  </ErrorBoundary>
</Parent>

Na Listagem 4, quaisquer erros de renderização em <Child> irá acionar a renderização alternativa do tratamento de erros <ErrorBoundary> componente. Você pode ver que os componentes do limite de erro atuam como uma espécie de bloco try/catch declarativo na visualização. Qualquer filho de <Child> também irá borbulhar até <ErrorBoundary> a menos que sejam capturados por algum outro limite de erro ao longo do caminho – também análogo ao comportamento try/catch.

Erros de JavaScript

Erros de JavaScript são tratados agrupando o código em blocos try/catch, como no JavaScript padrão. Isso é bem compreendido e funciona muito bem, mas há alguns comentários a serem feitos no contexto de uma UI React.

Primeiro, é importante observar que esses erros não se propagam para os componentes do limite de erro. É possível gerar erros manualmente por meio das propriedades funcionais normais do React e, assim, seria possível vincular o tratamento de erros à renderização condicional encontrada em seus limites de erro.

Erros de rede

Erros de rede ou de servidor decorrentes de chamadas de API devem ser tratados usando códigos de erro integrados, conforme mostrado na Listagem 5.

Listagem 5. Usando códigos de erro integrados


let response = await fetch(process.env.REACT_APP_API + 
'/api/describe?_id='+this.state.projectId, {
        headers: { "Authorization": this.props.userData.userData.jwt },
        method: 'GET',
      });
      if (response.ok){
        let json = await response.json();
        console.info(json);
        this.setState({ "project": json});
      } else {
        console.error("Problem: " + response);
        throw new Error(“Problem fetching user info”, response);
      }

O objetivo da Listagem 5 é usar os códigos de status HTTP padrão para determinar o estado de erro da solicitação de rede. (Às vezes é tentador usar um campo de “status” personalizado.) Nesse caso, o erro é detectado verificando response.oke, se houver um erro, geramos um novo erro com throw. Nesse caso, esperaríamos que um manipulador de captura lidasse com a situação.

Por fim, em relação aos erros de renderização e de JavaScript, lembre-se de que pode ser útil registrar erros por meio de uma API de relatório de erros remoto. Isso é tratado por componentes baseados em classe que implementam o componentDidCatch método.

Tratamento de erros em ação

Os exemplos de código neste artigo referem-se ao CodePen, que é derivado do exemplo encontrado nos documentos do React. Esta caneta oferece quatro condições de erro, cada uma representada por um símbolo colorido. div com um link. O link irá desencadear um erro. A linha inferior apresenta erros de JavaScript, a linha superior apresenta erros de renderização. A primeira coluna não é encapsulada com um limite de erro, a segunda coluna é agrupada.

Portanto, isso lhe dá uma visão do código e do comportamento de vários estados de erro possíveis:

  • A caixa verde gerará um erro de tempo de renderização quando clicada e será capturada por um limite, exibindo uma mensagem de erro.
  • A caixa vermelha gerará um erro de renderização sem limite e, como você pode ver, o aplicativo irá travar e interromper a renderização.
  • As caixas azul e roxa tratam os erros da mesma maneira, pois o limite do erro não captura o erro de JavaScript.

Vale a pena examinar esses exemplos, pois eles fornecem todos os elementos funcionais dos limites de erro em um pacote de tamanho de bits.

Você também pode achar útil verificar este exemplo CodePen de limites de erro no React 16.

Conclusão

Você pode pensar nos limites de erro como blocos declarativos de captura de erros para sua marcação de visualização. A partir do React 16, se a renderização em um componente causar um erro, a árvore inteira do componente não será renderizada. Caso contrário, o erro surgirá até que o primeiro componente de tratamento de erros seja encontrado. Antes do React 16, os erros deixavam a árvore de componentes parcialmente renderizada.

Os componentes do limite de erro devem ser baseados em classes, embora haja planos para adicionar suporte a ganchos para o ciclo de vida.

Como vimos, a ideia básica é criar um componente que seja renderizado condicionalmente com base no estado de erro. Existem duas maneiras de fazer isso: a componentDidCatch() método ou estático getDerivedStateFromError() método.