Aprenda como categorizar e tratar erros de forma eficaz em Error Boundaries do React, melhorando a estabilidade da aplicação e a experiência do usuário.
Categorização de Erros em Error Boundaries do React: Um Guia Completo
O tratamento de erros é um aspeto crítico na construção de aplicações React robustas e fáceis de manter. Embora os Error Boundaries do React forneçam um mecanismo para tratar de forma elegante os erros que ocorrem durante a renderização, entender como categorizar e responder a diferentes tipos de erro é crucial para criar uma aplicação verdadeiramente resiliente. Este guia explora várias abordagens para a categorização de erros dentro dos Error Boundaries, oferecendo exemplos práticos e insights acionáveis para melhorar a sua estratégia de gestão de erros.
O que são os Error Boundaries do React?
Introduzidos no React 16, os Error Boundaries são componentes React que capturam erros de JavaScript em qualquer parte da sua árvore de componentes filhos, registam esses erros e exibem uma UI de fallback em vez de quebrar toda a árvore de componentes. Eles funcionam de forma semelhante a um bloco try...catch, mas para componentes.
Principais características dos Error Boundaries:
- Tratamento de Erros a Nível de Componente: Isola erros em subárvores de componentes específicas.
- Degradação Graciosa: Impede que a aplicação inteira quebre devido a um erro num único componente.
- UI de Fallback Controlada: Exibe uma mensagem amigável ao utilizador ou conteúdo alternativo quando ocorre um erro.
- Registo de Erros: Facilita o rastreamento e a depuração de erros, registando as informações do erro.
Porquê Categorizar Erros nos Error Boundaries?
Apenas capturar erros não é suficiente. Um tratamento de erros eficaz requer entender o que correu mal e responder adequadamente. Categorizar erros dentro dos Error Boundaries oferece vários benefícios:
- Tratamento de Erros Direcionado: Diferentes tipos de erro podem exigir diferentes respostas. Por exemplo, um erro de rede pode justificar um mecanismo de nova tentativa, enquanto um erro de validação de dados pode exigir a correção da entrada do utilizador.
- Melhor Experiência do Utilizador: Exibe mensagens de erro mais informativas com base no tipo de erro. Uma mensagem genérica "Algo correu mal" é menos útil do que uma mensagem específica que indica um problema de rede ou uma entrada inválida.
- Depuração Aprimorada: A categorização de erros fornece um contexto valioso para a depuração e identificação da causa raiz dos problemas.
- Monitorização Proativa: Rastreia a frequência de diferentes tipos de erro para identificar problemas recorrentes e priorizar correções.
- UI de Fallback Estratégica: Exibe diferentes UIs de fallback dependendo do erro, fornecendo informações ou ações mais relevantes para o utilizador.
Abordagens para a Categorização de Erros
Várias técnicas podem ser empregadas para categorizar erros dentro dos Error Boundaries do React:
1. Usando instanceof
O operador instanceof verifica se um objeto é uma instância de uma classe específica. Isso é útil para categorizar erros com base nos seus tipos de erro incorporados ou personalizados.
Exemplo:
class NetworkError extends Error {
constructor(message) {
super(message);
this.name = "NetworkError";
}
}
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// Atualiza o estado para que a próxima renderização mostre a UI de fallback.
return { hasError: true, error: error };
}
componentDidCatch(error, errorInfo) {
// Você também pode registar o erro num serviço de relatório de erros
console.error("Erro capturado:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
// Você pode renderizar qualquer UI de fallback personalizada
let errorMessage = "Algo correu mal.";
if (this.state.error instanceof NetworkError) {
errorMessage = "Ocorreu um erro de rede. Por favor, verifique a sua conexão e tente novamente.";
} else if (this.state.error instanceof ValidationError) {
errorMessage = "Houve um erro de validação. Por favor, reveja os seus dados.";
}
return (
<div>
<h2>Erro!</h2>
<p>{errorMessage}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
Explicação:
- Classes personalizadas
NetworkErroreValidationErrorsão definidas, estendendo a classeErrorincorporada. - No método
renderdo componenteMyErrorBoundary, o operadorinstanceofé usado para verificar o tipo do erro capturado. - Com base no tipo de erro, uma mensagem de erro específica é exibida na UI de fallback.
2. Usando Códigos ou Propriedades de Erro
Outra abordagem é incluir códigos ou propriedades de erro no próprio objeto de erro. Isso permite uma categorização mais detalhada com base em cenários de erro específicos.
Exemplo:
function fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(response => {
if (!response.ok) {
const error = new Error("Falha na requisição de rede");
error.code = response.status; // Adiciona um código de erro personalizado
reject(error);
}
return response.json();
})
.then(data => resolve(data))
.catch(error => reject(error));
});
}
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// Atualiza o estado para que a próxima renderização mostre a UI de fallback.
return { hasError: true, error: error };
}
componentDidCatch(error, errorInfo) {
// Você também pode registar o erro num serviço de relatório de erros
console.error("Erro capturado:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
let errorMessage = "Algo correu mal.";
if (this.state.error.code === 404) {
errorMessage = "Recurso não encontrado.";
} else if (this.state.error.code >= 500) {
errorMessage = "Erro no servidor. Por favor, tente novamente mais tarde.";
}
return (
<div>
<h2>Erro!</h2>
<p>{errorMessage}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
Explicação:
- A função
fetchDataadiciona uma propriedadecodeao objeto de erro, representando o código de status HTTP. - O componente
MyErrorBoundaryverifica a propriedadecodepara determinar o cenário de erro específico. - Diferentes mensagens de erro são exibidas com base no código de erro.
3. Usando um Mapeamento de Erros Centralizado
Para aplicações complexas, manter um mapeamento de erros centralizado pode melhorar a organização e a manutenibilidade do código. Isso envolve a criação de um dicionário ou objeto que mapeia tipos ou códigos de erro para mensagens de erro específicas e lógica de tratamento.
Exemplo:
const errorMap = {
"NETWORK_ERROR": {
message: "Ocorreu um erro de rede. Por favor, verifique a sua conexão.",
retry: true,
},
"INVALID_INPUT": {
message: "Entrada inválida. Por favor, reveja os seus dados.",
retry: false,
},
404: {
message: "Recurso não encontrado.",
retry: false,
},
500: {
message: "Erro no servidor. Por favor, tente novamente mais tarde.",
retry: true,
},
"DEFAULT": {
message: "Algo correu mal.",
retry: false,
},
};
function handleCustomError(errorType) {
const errorDetails = errorMap[errorType] || errorMap["DEFAULT"];
return errorDetails;
}
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, errorDetails: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// Atualiza o estado para que a próxima renderização mostre a UI de fallback.
const errorDetails = handleCustomError(error.message);
return { hasError: true, errorDetails: errorDetails };
}
componentDidCatch(error, errorInfo) {
// Você também pode registar o erro num serviço de relatório de erros
console.error("Erro capturado:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
const { message } = this.state.errorDetails;
return (
<div>
<h2>Erro!</h2>
<p>{message}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.errorDetails.message}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
function MyComponent(){
const [data, setData] = React.useState(null);
React.useEffect(() => {
try {
throw new Error("NETWORK_ERROR");
} catch (e) {
throw e;
}
}, []);
return <div></div>;
}
Explicação:
- O objeto
errorMaparmazena informações de erro, incluindo mensagens e sinalizadores de nova tentativa, com base em tipos ou códigos de erro. - A função
handleCustomErrorrecupera detalhes do erro doerrorMapcom base na mensagem de erro e retorna valores padrão se nenhum código específico for encontrado. - O componente
MyErrorBoundaryusahandleCustomErrorpara obter a mensagem de erro apropriada doerrorMap.
Melhores Práticas para Categorização de Erros
- Defina Tipos de Erro Claros: Estabeleça um conjunto consistente de tipos ou códigos de erro para a sua aplicação.
- Forneça Informações Contextuais: Inclua detalhes relevantes nos objetos de erro para facilitar a depuração.
- Centralize a Lógica de Tratamento de Erros: Use um mapeamento de erros centralizado ou funções utilitárias para gerir o tratamento de erros de forma consistente.
- Registe Erros Eficazmente: Integre com serviços de relatório de erros para rastrear e analisar erros em produção. Serviços populares incluem Sentry, Rollbar e Bugsnag.
- Teste o Tratamento de Erros: Escreva testes unitários para verificar se os seus Error Boundaries tratam corretamente diferentes tipos de erro.
- Considere a Experiência do Utilizador: Exiba mensagens de erro informativas e amigáveis que guiem os utilizadores para a resolução. Evite jargão técnico.
- Monitorize as Taxas de Erro: Rastreie a frequência de diferentes tipos de erro para identificar problemas recorrentes e priorizar correções.
- Internacionalização (i18n): Ao apresentar mensagens de erro ao utilizador, garanta que as suas mensagens sejam devidamente internacionalizadas para suportar diferentes idiomas e culturas. Use bibliotecas como
i18nextou a Context API do React para gerir as traduções. - Acessibilidade (a11y): Certifique-se de que as suas mensagens de erro são acessíveis a utilizadores com deficiência. Use atributos ARIA para fornecer contexto adicional aos leitores de ecrã.
- Segurança: Tenha cuidado com as informações que exibe nas mensagens de erro, especialmente em ambientes de produção. Evite expor dados sensíveis que possam ser explorados por invasores. Por exemplo, não exiba stack traces brutos para os utilizadores finais.
Cenário de Exemplo: Tratando Erros de API numa Aplicação de E-commerce
Considere uma aplicação de e-commerce que recupera informações de produtos de uma API. Cenários de erro potenciais incluem:
- Erros de Rede: O servidor da API está indisponível ou a conexão à internet do utilizador é interrompida.
- Erros de Autenticação: O token de autenticação do utilizador é inválido ou expirou.
- Erros de Recurso Não Encontrado: O produto solicitado não existe.
- Erros de Servidor: O servidor da API encontra um erro interno.
Usando Error Boundaries e categorização de erros, a aplicação pode tratar estes cenários de forma elegante:
// Exemplo (Simplificado)
async function fetchProduct(productId) {
try {
const response = await fetch(`/api/products/${productId}`);
if (!response.ok) {
if (response.status === 404) {
throw new Error("PRODUCT_NOT_FOUND");
} else if (response.status === 401 || response.status === 403) {
throw new Error("AUTHENTICATION_ERROR");
} else {
throw new Error("SERVER_ERROR");
}
}
return await response.json();
} catch (error) {
if (error instanceof TypeError && error.message === "Failed to fetch") {
throw new Error("NETWORK_ERROR");
}
throw error;
}
}
class ProductErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, errorDetails: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
const errorDetails = handleCustomError(error.message); // Usa o errorMap como mostrado anteriormente
return { hasError: true, errorDetails: errorDetails };
}
componentDidCatch(error, errorInfo) {
console.error("Erro capturado:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
const { message, retry } = this.state.errorDetails;
return (
<div>
<h2>Erro!</h2>
<p>{message}</p>
{retry && <button onClick={() => window.location.reload()}>Tentar Novamente</button>}
</div>
);
}
return this.props.children;
}
}
Explicação:
- A função
fetchProductverifica o código de status da resposta da API e lança tipos de erro específicos com base no status. - O componente
ProductErrorBoundarycaptura esses erros e exibe mensagens de erro apropriadas. - Para erros de rede e de servidor, um botão "Tentar Novamente" é exibido, permitindo ao utilizador tentar a requisição novamente.
- Para erros de autenticação, o utilizador pode ser redirecionado para a página de login.
- Para erros de recurso não encontrado, é exibida uma mensagem indicando que o produto não existe.
Conclusão
Categorizar erros dentro dos Error Boundaries do React é essencial para construir aplicações resilientes e amigáveis ao utilizador. Ao empregar técnicas como verificações com instanceof, códigos de erro e mapeamentos de erros centralizados, pode tratar eficazmente diferentes cenários de erro e proporcionar uma melhor experiência do utilizador. Lembre-se de seguir as melhores práticas para tratamento de erros, registo e teste para garantir que a sua aplicação trata situações inesperadas de forma elegante.
Ao implementar estas estratégias, pode melhorar significativamente a estabilidade e a manutenibilidade das suas aplicações React, proporcionando uma experiência mais suave e fiável para os seus utilizadores, independentemente da sua localização ou contexto.
Recursos Adicionais: