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.

  1. 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.

  2. 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")
    
    // ...
    
  3. 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.

  4. 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
    }
    
  5. 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,
        }, {
    // ...
    
  6. 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>
    }
    
  7. 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 />
        }, {
    // ...
    
  8. 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
    }