Artigo original: React Router Tutorial – How to Render, Redirect, Switch, Link, and More, With Code Examples

Tradução em português continental (europeu)

Se só agora começou com React, provavelmente a sua cabeça ainda está a dar voltas relativamente ao conceito de Single-Page App (SPA) ou Aplicação de Página Única.

Tradicionalmente, o roteamento funciona assim: vamos imaginar que escreveu /contact como URL. O navegador, então, fará uma solicitação GET para o servidor e este devolverá uma página HTML como resposta.

Com o novo paradigma de SPA, no entanto, todos as solicitações do URL são servidos usando código do lado do client.

Se aplicarmos isto ao contexto do React, cada página será um componente do React. O React-Router combina o URL e carrega o componente para essa página específica.

Tudo acontece tão rápido e na perfeição, que o utilizador obtém uma experiência parecida com uma aplicação nativa no navegador. Não há uma página branca entre as transições de roteamento.

Neste artigo, aprenderemos como usar o React-Router e seus componentes para criar uma Single-Page App. Para isso, vamos abrir o nosso editor de texto favorito e vamos começar.

Configurar o projecto

Vamos criar um novo projecto do React correndo o seguinte comando:

yarn create react-app react-router-demo

Usaremos o yarn para instalar dependências, mas, em alternativa, também poderá ser utilizado o npm.

A seguir, vamos instalar o react-router-dom.

yarn add react-router-dom

Para o estilo dos componentes, usaremos o framework Bulma CSS. Por isso, vamos adicioná-la também.

yarn add bulma

A seguir, vamos importar bulma.min.css no ficheiro index.js e limpar todo o código desnecessário do ficheiro App.js.

import "bulma/css/bulma.min.css";

Agora que temos o projecto configurado, vamos começar por criar alguns componentes da página.

Criar os componentes da página

Vamos criar a pasta pages dentro da pasta src, onde colocaremos todos os componentes da página.

Para esta demonstração, vamos criar três páginas - a página principal (Home), Sobre (About) e a página do perfil (Profile).

Vamos colar o seguinte código nos componentes Home e About.

// pages/Home.js

import React from "react";

const Home = () => (
  <div>
    <h1 className="title is-1">Esta é a página principal</h1>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras gravida,
      risus at dapibus aliquet, elit quam scelerisque tortor, nec accumsan eros
      nulla interdum justo. Pellentesque dignissim, sapien et congue rutrum,
      lorem tortor dapibus turpis, sit amet vestibulum eros mi et odio.
    </p>
  </div>
);

export default Home;
// pages/About.js

import React from "react";

const About = () => (
  <div>
    <h1 className="title is-1">Esta é a página Sobre</h1>
    <p>
      Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
      inceptos himenaeos. Vestibulum ante ipsum primis in faucibus orci luctus
      et ultrices posuere cubilia curae; Duis consequat nulla ac ex consequat,
      in efficitur arcu congue. Nam fermentum commodo egestas.
    </p>
  </div>
);

export default About;

Mais tarde, no artigo, criaremos a página de perfil.

Criar o componente Navbar

Vamos começar por criar a barra de navegação para a nossa aplicação. Este componente fará uso do componente <NavLink /> do react-router-dom.

Vamos criar a pasta "components" dentro da pasta src.

// components/Navbar.js

import React, { useState } from "react";
import { NavLink } from "react-router-dom";

const Navbar = () => {
  const [isOpen, setOpen] = useState(false);
  return ( 
  	<nav
      className="navbar is-primary"
      role="navigation"
      aria-label="main navigation"
    >
      <div className="container">
      	{/* ... */}
      </div>
    </nav>
  );
 };
 
 export default Navbar;

A variável de estado isOpen será usada para acionar o menu nos dispositivos móveis ou tablets.

Para isso, vamos adicionar o menu hamburguer.

const Navbar = () => {
  const [isOpen, setOpen] = useState(false);
  return ( 
  	<nav
      className="navbar is-primary"
      role="navigation"
      aria-label="main navigation"
    >
      <div className="container">
      <div className="navbar-brand">
          <a
            role="button"
            className={`navbar-burger burger ${isOpen && "is-active"}`}
            aria-label="menu"
            aria-expanded="false"
            onClick={() => setOpen(!isOpen)}
          >
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
          </a>
        </div>
      	{/* ... */}
      </div>
    </nav>
  );
 };

Para adicionar o link no menu, vamos usar o componente <NavLink /> do react-router-dom.

O componente NavLink fornece uma forma declarativa para navegar pela aplicação. É parecido com o componente Link, à excepção de que se pode aplicar um estilo activo no link se ele estiver activo.

Para especificar qual a rota para onde navegar, usamos a propriedade to e passamos o nome do caminho.

A propriedade activeClassName adicionará uma classe active no link se ele estiver activo.

<NavLink
    className="navbar-item"
    activeClassName="is-active"
    to="/"
    exact
>
	Home
</NavLink>

No navegador, o componente NavLink é renderizado como uma marca <a> com o atributo href com o valor que foi passado na propriedade to.

Além disso, aqui vamos especificar a propriedade exact para que combine precisamente com o URL.

Vamos adicionar todos os links e terminar o componente Navbar.

import React, { useState } from "react";
import { NavLink } from "react-router-dom";

const Navbar = () => {
  const [isOpen, setOpen] = useState(false);
  return (
    <nav
      className="navbar is-primary"
      role="navigation"
      aria-label="main navigation"
    >
      <div className="container">
        <div className="navbar-brand">
          <a
            role="button"
            className={`navbar-burger burger ${isOpen && "is-active"}`}
            aria-label="menu"
            aria-expanded="false"
            onClick={() => setOpen(!isOpen)}
          >
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
            <span aria-hidden="true"></span>
          </a>
        </div>

        <div className={`navbar-menu ${isOpen && "is-active"}`}>
          <div className="navbar-start">
            <NavLink className="navbar-item" activeClassName="is-active" to="/">
              Página Principal
            </NavLink>

            <NavLink
              className="navbar-item"
              activeClassName="is-active"
              to="/about"
            >
              Sobre
            </NavLink>

            <NavLink
              className="navbar-item"
              activeClassName="is-active"
              to="/profile"
            >
              Perfil
            </NavLink>
          </div>

          <div className="navbar-end">
            <div className="navbar-item">
              <div className="buttons">
                <a className="button is-white">Log in</a>
              </div>
            </div>
          </div>
        </div>
      </div>
    </nav>
  );
};

export default Navbar;

De notar que adicionámos aqui um botão de Login. Voltaremos ao componente Navbar outra vez quando discutirmos as rotas protegidas, mais à frente neste guia.

Renderizar as páginas

Agora que o componente Navbar está configurado, vamos adicioná-lo à página e começar a renderizar as páginas.

Uma vez que a barra de navegação é um componente comum a todas as páginas, em vez de chamar o componente em cada uma das páginas, utilizaremos uma abordagem diferente e optaremos por renderizar a Navbar em um layout comum.

// App.js

function App() {
  return (
    <>
      <Navbar />
      <div className="container mt-2" style={{ marginTop: 40 }}>
        {/* Renderize a página aqui */}
      </div>
    </>
  );
}

Agora, vamos adicionar os componentes da página ao container.

// App.js

function App() {
  return (
    <>
      <Navbar />
      <div className="container mt-2" style={{ marginTop: 40 }}>
        <Home />
      	<About />
      </div>
    </>
  );
}

Se virmos os resultados agora, vamos notar que ambos os componentes de página, da página principal e de Sobre são renderizados na página. Isso acontece porque ainda não adicionámos nenhuma lógica de roteamento.

Vamos precisar de importar o componente BrowserRouter do React Router para adicionar a capacidade de rotear os componentes. Tudo o que precisamos de fazer é envolver todos os componentes da página dentro do componente BrowserRouter. Isto fará com que todos os componentes da página tenham lógica de roteamento. Perfeito!

Mais uma vez, no entanto, nada vai mudar com os resultados - ainda veremos ambas as páginas a ser renderizadas. Precisamos de renderizar o componente da página só quando o URL combina com um caminho em particular. É aqui que o componente Route do React Router entra em jogo.

O componente Router tem uma propriedade path, que aceita o caminho da página. O componente da página deverá ser envolvido com o Router, como mostrado abaixo:

<Route path="/about">
  <About />
</Route>

Vamos fazer o mesmo para o componente Home.

<Route exact path="/">
  <Home />
</Route>

A propriedade exact acima, diz ao componente Router para combinar exactamente com o caminho. Se não adicionarmos a propriedade exact o caminho /, combinará com todas as rotas a começar com /, incluindo /about.

Se formos agora ver os resultados, continuaremos a ver ambos os componentes a serem renderizados. No entanto, se formos a /about, notaremos que somente o componente About é renderizado. Este comportamento acontece porque o roteador continua a combinar o URL com as rotas, mesmo depois de já ter combinado com a rota inicial.

Temos então de dizer ao roteador para parar de fazer combinações assim que combinar com uma rota. Isto é feito usando o componente Switch do React Router.

function App() {
  return (
    <BrowserRouter>
      <Navbar />
      <div className="container mt-2" style={{ marginTop: 40 }}>
        <Switch>
          <Route exact path="/">
            <Home />
          </Route>
          <Route path="/about">
            <About />
          </Route>
        </Switch>
      </div>
    </BrowserRouter>
  );
}

E aqui temos! Configurámos com sucesso o roteamento na nossa aplicação React.

Rotas protegidas e redirecionamentos

Ao trabalhar com aplicações do mundo real, teremos determinadas rotas por trás de sistemas de autenticação. Teremos rotas ou páginas que só conseguirão ser acedidas por um utilizador autenticado. Nesta secção, aprenderemos como implementar estas rotas.

Por favor, observe-se que não criaremos nenhum formulário de autenticação nem teremos um servidor para autenticar utilizadores. Em uma aplicação real, não implementaremos a autenticação da forma como é demonstrada aqui.

Vamos criar o componente da pagina de perfil, que so deverá ser acedido por um utilizador autenticado.

// pages/Profile.js

import { useParams } from "react-router-dom";

const Profile = () => {
  const { name } = useParams();
  return (
    <div>
      <h1 className="title is-1">Esta é a página de perfil</h1>
      <article className="message is-dark" style={{ marginTop: 40 }}>
        <div className="message-header">
          <p>{name}</p>
        </div>
        <div className="message-body">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit.{" "}
          <strong>Pellentesque risus mi</strong>, tempus quis placerat ut, porta
          nec nulla. Vestibulum rhoncus ac ex sit amet fringilla. Nullam gravida
          purus diam, et dictum <a>felis venenatis</a> efficitur. Aenean ac{" "}
          <em>eleifend lacus</em>, in mollis lectus. Donec sodales, arcu et
          sollicitudin porttitor, tortor urna tempor ligula, id porttitor mi
          magna a neque. Donec dui urna, vehicula et sem eget, facilisis sodales
          sem.
        </div>
      </article>
    </div>
  );
};

Vamos pegar o nome do utilizador vindo do URL usando os parâmetros da rota.

Adicionamos a rota Profile (perfil) no roteador.

<Route path="/profile/:name">
  <Profile />
</Route>

Neste momento a página de perfil pode ser acedida diretamente. Por isso, para a tornar uma rota autenticada, vamos criar um componente de alta ordem (em inglês, HOC ou high-order component) para envolver a lógica de autenticação.

const withAuth = (Component) => {
  const AuthRoute = () => {
    const isAuth = !!localStorage.getItem("token");
    // ...
  };

  return AuthRoute;
};

Para determinar se um utilizador está autenticado ou não, usamos o token de autenticação guardado no navegador quando o utilizador se autentica. Se o utilizador não estiver autenticado, redirecionamos o utilizador para a página principal. O componente Redirect do React Router pode ser usado para redirecionar o utilizador para outro caminho.

const withAuth = (Component) => {
  const AuthRoute = () => {
    const isAuth = !!localStorage.getItem("token");
    if (isAuth) {
      return <Component />;
    } else {
      return <Redirect to="/" />;
    }
  };

  return AuthRoute;
};

Também poderemos passar outros tipos de informação do utilizador, tais como o nome e o ID de utilizador, usando as propriedades do componente envolvente.

De seguida, usamos o HOC withAuth no componente de perfil.

import withAuth from "../components/withAuth";

const Profile = () => {
 // ...
}

export default withAuth(Profile);

Agora, se tentarmos visitar /profile/JohnDoe, seremos redirecionados para a página principal. Isto acontece porque o token de autenticação não está gravado na memória do nosso navegador.

Muito bem, então, vamos voltar para o componente da Navbar e adicionar as funcionalidades de Login e Logout. Quando o utilizador é autenticado, mostra o botão de Logout, mas quando o utilizador não está autenticado, mostramos o botão de Login.

// components/Navbar.js

const Navbar = () => {
	// ...
    return (
    	<nav
          className="navbar is-primary"
          role="navigation"
          aria-label="main navigation"
        >
        <div className="container">
        	{/* ... */}
            <div className="navbar-end">
            <div className="navbar-item">
              <div className="buttons">
                {!isAuth ? (
                  <button className="button is-white" onClick={loginUser}>
                    Login
                  </button>
                ) : (
                  <button className="button is-black" onClick={logoutUser}>
                    Logout
                  </button>
                )}
              </div>
            </div>
          </div>
        </div>
        </nav>
    );
}

Quando o utilizador clica no botão de Login, vamos colocar um token falso na memória local e redirecionar o utilizador para a página de perfil.

Não poderemos, no entanto, utilizar o componente Redirect neste caso – precisamos de direcionar o utilizador programaticamente. Por razões de segurança, os tokens sensíveis usados para autenticação são normalmente guardados nos cookies.

O React Router tem o HOC withRouter, que tira partido da API History ao injectar o objecto history nas propriedades do componente. Para além disso, passa para o componente envolvente as propriedades match e location.

// components/Navbar.js

import { NavLink, withRouter } from "react-router-dom";

const Navbar = ({ history }) => { 
  const isAuth = !!localStorage.getItem("token");

  const loginUser = () => {
    localStorage.setItem("token", "some-login-token");
    history.push("/profile/Vijit");
  };

  const logoutUser = () => {
    localStorage.removeItem("token");
    history.push("/");
  };
  
  return ( 
   {/* ... */}
  );
};

export default withRouter(Navbar);

E voilà! É isto. Também conquistámos a terra das rotas autenticadas. Assista a esta apresentação aqui e encontre o código completo neste repositório para referência.

Conclusão

Espero que agora já tenha uma pequena ideia de como funciona o roteamento em geral no lado do cliente e de como implementar o roteamento em React utilizando a biblioteca React Router.

Neste guia, aprendemos os componentes vitais do React Router como Route,  withRouter, Link e por aí em diante, mas também alguns conceitos avançados como rotas autenticadas, que são necessárias para construir uma aplicação.

Verifica a documentação do React Router para obter uma visão mais detalhada de cada um dos componentes.