E aí, programador! o/
Nessa aula você vai aprender como usar o React Router para lidar com data fetching e tratamento de erros. Esses são dois pontos muito importantes do desenvolvimento front-end e o React Router fornece uma solução fácil e organizada de lidar com eles em SPAs.
Para essa aula iremos continuar de onde paramos na anterior. Vamos usar como exemplo a página individual de produto para sermos mais objetivos, mas todos os conhecimentos aqui se aplicam a qualquer contexto e podem ser adaptados para outras páginas.
No momento, nossa página de produto não tem nenhum tipo de tratamento de erro e estamos fazendo a busca dos dados (por mais que não seja uma API real) dentro do próprio componente. Se lançássemos um erro nesse componente veríamos a tela padrão de erro do React Router:
import { Link, useParams } from "react-router-dom"
import products from "../database.json"
export default function Product() {
const { productId } = useParams()
const product = products.find(p => p.id === +productId)
throw new Error("Erro qualquer")
// ...
Para solucionar esse problema o React Router fornece loaders para lidar com o carregamento de dados necessários para um componente e elementos de ErrorBoundary para tratar os erros.
Vamos começar pelo carregamentos de dados. Crie uma pasta “loaders” e dentro um arquivo chamado “products.js”:
Obs.: repare que aqui não precisamos do useParams, porque o próprio React Router já fornece os parâmetros “request”, “params” e “context” para os loaders.
import products from "../database.json"
export function loadProduct({ params }) {
const product = products.find(p => p.id === +params.productId)
return product
}
Com o loader pronto, adicione-o ao “router.jsx”:
// ...
import Product from "./pages/Product";
import { loadProduct } from "./loaders/products";
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [{
path: "products",
element: <Products />
}, {
path: "products/:productId",
element: <Product />,
loader: loadProduct,
}, {
// ...
Certo, separamos o carregamento dos dados, mas ainda precisamos lidar melhor com os erros. Para isso, crie uma pasta chamadas “error-boundaries” e dentro dela o componente “ProductBoundary.jsx”:
Obs.: repare que tudo que precisamos fazer aqui é usar o hook useRouteError do React Router e então criar um fallback apropriado.
Obs².: repare ainda que podemos contar com a função auxiliar isRouteErrorResponse() para verificar se é um erro de resposta http.
import { isRouteErrorResponse, useRouteError } from "react-router-dom";
export default function ProductBoundary() {
const error = useRouteError()
if (isRouteErrorResponse(error)) {
switch (error.status) {
case 404:
return <h2>Oops... Produto não encontrado =(</h2>
case 401:
return <h2>Você não está autorizado a ver essa página.</h2>
case 400:
return <h2>Parece que algo deu errado na requisição.</h2>
case 500:
return <h2>Erro interno no servidor.</h2>
}
}
return <h2>Algo deu errado.</h2>
}
Agora adicione o ErrorBoundary no arquivo “router.jsx”:
// ...
import Product from "./pages/Product";
import { loadProduct } from "./loaders/products";
import ProductBoundary from "./error-boundaries/ProductBoundary";
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [{
path: "products",
element: <Products />
}, {
path: "products/:productId",
element: <Product />,
loader: loadProduct,
errorElement: <ProductBoundary />
}, {
// ...
Por fim, podemos testar adicionando alguns erros de propósito no loader:
Obs.: repare que o React Router mostra o erro exatamento no componente que o lançou, não afentando os componentes mais externos, como o layout da página
import products from "../database.json"
export function loadProduct({ params }) {
const product = products.find(p => p.id === +params.productId)
if (!product) {
throw new Response("Oops... Esse produto não foi encontrado =(", { status: 404 })
}
// Exemplo de erro, descomente para testar
// throw new Response("Não autorizado", { status: 401 })
// Exemplo de erro, descomente para testar
// throw new Response("Erro no servidor!", { status: 500 })
return product
}