Roteamento no React com os poderes do React Router v4

Logo do React Router

Vira e mexe eu vejo alguém falando que vai usar React pra fazer uma Single Page Application com várias rotas e tudo mais e sempre rola algum comentário que pra fazer isso no React é um pouco difícil. Me baseando nisso, resolvi escrever esse post para mostrar uma forma mais fácil (ou não) pra conseguir lidar com várias páginas na sua aplicação React :)

“First things first”

Como falei, a ideia é fazer a implementação mais simples do universo onde vamos criar uma Home e uma página de Sobre, então para nos ajudar nessa missão de simplicidade e não precisarmos configurar nada nem mesmo as tretas do Webpack com o React, vamos de create-react-app para iniciar nosso projeto (caso você já conheça a ferramenta, pode pular direto para o próximo tópico).

npm install -g create-react-app

Depois de instalado o create-react-app navegue até uma pasta de sua preferência pelo terminal e rode o comando:

create-react-app react-com-rotas

O próximo passo para ver a aplicação funcionando é acessar a pasta criada com um cd react-com-rotas e rodar o comando npm start para ver essa aba abrindo magicamente no seu browser:

Imagem da Aplicação base que o create-react-app gera.

Por onde começar com as páginas?

Dado a aba que foi aberta no browser, temos um forte indicio que uma página da nossa aplicação já está pronta no caso a Home.

Podemos ver seu código abrindo o arquivo:

####### ./src/App.js

import React, { Component } from ‘react’;
import logo from ‘./logo.svg’;
import ‘./App.css’;
class App extends Component {
render() {
return (
<div className=”App”>
<header className=”App-header”>
<img src={logo} className=”App-logo” alt=”logo” />
<h1 className=”App-title”>Welcome to React</h1>
</header>
<p className=”App-intro”>
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;

Para facilitar a criação da página Sobre vamos somente criar um novo arquivo ./src/Sobre.js e copiar e colar o conteúdo do ./src/App.js alterando: o nome da classe, o título da página e o conteúdo ficando assim:

####### ./src/Sobre.js

import React, { Component } from ‘react’;
import logo from ‘./logo.svg’;
import ‘./App.css’;
class Sobre extends Component {
render() {
return (
<div className=”App”>
<header className=”App-header”>
<img src={logo} className=”App-logo” alt=”logo” />
<h1 className=”App-title”>Página de Sobre</h1>
</header>
<p className=”App-intro”>
Exemplo de Página Sobre :)
</p>
</div>
);
}
}
export default Sobre;

A ideia é que agora, ao digitarmos na barra de endereço do navegador http://localhost:3001/sobre/ o componente Sobre seja carregado, e se digitarmos http://localhost:3001/ o componente App.

Como decidir qual componente aparece?

A treta começa aqui, por padrão a página que sempre aparece é a Home que é representada pelo nosso App component (<App />). Isso acontece porque ele é o componente que é injetado por padrão no html da página por meio da funçãoReactDOM.render() que é a encarregada de fazer o meio de campo entre o nosso JSX e o HTML da Página. Essa função fica dentro do arquivo index.js dentro da pasta src:

####### ./src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App'; // Import do componente App
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
Mas se só o App tá ali pra aparecer no HTML, eu tenho que fazer o import do componente Sobre e colocar ele no ReactDOM.render junto com o App então?

Sim! E não também! Se só fizermos o import e colocarmos o componente no ReactDOM.render, vamos receber um errinho mais ou menos assim:

Erro ao passar dois componentes para a função render do ReactDOM

A função render só recebe um componente por vez, ou um array de componentes [<App />,<Sobre />]. Mesmo passando dois componentes (ambos dentro de um array), o resultado que aparecerá vai ser algo nessa linha:

Resultado no browser ao passar os nossos dois componentes que representam as páginas na função render.

Para resolver isso tudo, precisamos decidir quando cada componente vai aparecer, algo nessa linha:

if(window.location.pathname === '/sobre') {
<Sobre />
} else {
<App />
}

Mas o ReactDOM.render() não aceita de boas esse código:

Erro ao passar um if() dentro do render

Pra ajudar a gente a lidar com isso, existe uma biblioteca bem famosa que é o react-router, ela abstrai toda a lógica para fazer essas verificações além de facilitar a navegação por links sem refresh por meio de componentes, trazendo algo mais declarativo do que sair jogando um monte de if no meio do código.

Como assim cara?

Vamos ver agora!

Utilizando o React Router

Primeiro de tudo, devemos instalar a biblioteca rodando um:

npm install --save react-router-dom

Feito isso, devemos importar um cara chamado BrowserRouter, ele é um componente que irá ser responsável por informar pra nossa aplicação que a partir de onde ele é chamado teremos um roteamento de componentes, por conta disso ele irá ficar em volta tanto do <App /> quanto do <Sobre />. Com isso o código do nosso ./src/index.js fica assim:

####### ./src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import Sobre from './Sobre';
import registerServiceWorker from './registerServiceWorker';
import { BrowserRouter } from 'react-router-dom' // importando o BrowserRouter do pacote que acabamos de instalar
ReactDOM.render(
<BrowserRouter>
<App />
<Sobre />
</ BrowserRouter>
, document.getElementById('root'));
registerServiceWorker();

Com nosso router adicionado, tudo o que precisamos fazer é salvar, olhar o browser e…

Ops, mais um erro, mas esse já é novo "Um <Router> deve ter somente um elemento filho"

O erro faz todo sentido, o router vai mostrar somente um componente, no caso, uma página por vez. Pra ajudar ele nessa tarefa, precisamos importar mais dois carinhas do react-router-dom:

import { BrowserRouter, Switch, Route } from 'react-router-dom'

O Switch, é um componente que recebe vários componentes Route e dado o caminho que for passado na URL um deles é renderizado. Cada Route é uma rota do nosso sistema, e devemos passar pra ele qual vai ser o caminho da url por meio de um atributo path="" e dado esse path, um outro atributo com o nosso componente que foi importado chamado component={ComponenteDoPath}, deixando nossa estrutura com esse formato:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import Sobre from './Sobre';
import registerServiceWorker from './registerServiceWorker';
import { BrowserRouter, Switch, Route } from 'react-router-dom'
ReactDOM.render(
<BrowserRouter>
<Switch>
<Route path="/" exact={true} component={App} />
<Route path="/sobre" component={Sobre} />
</Switch>
</ BrowserRouter>
, document.getElementById('root'));
registerServiceWorker();

Vale ressaltar que o path="/" pode ser combinado com um atributo extra chamado exact para garantir que se a rota tiver somente "/" ele vai ser renderizado e evitar conflito com as outras rotas que possuam o valor passado no path.

Uma outra parada legal do path, é que se passarmos um Route com o path sendo um * após o último route (em nosso caso após o Route do sobre), podemos ter uma rota que representa a página 404 do nosso sistema.

<Route path='*' component={ComponenteDePagina404} />

Adicionando esses caras na nossa aplicação, agora é possível acessar: http://localhost:3001/sobre e visualizar nosso componente :)

Pera, mas como fazer pra trocar de página via link sem atualizar igual as PWAs que todo mundo fala tanto?

Navegando entre páginas sem refresh

Esse é um bom ponto, e essencial, para torná-lo real tudo o que precisamos fazer é acessar o nosso ./src/App.js e ao invés de colocar uma tag <a href="/sobre"> que sempre faz um refresh no browser, devemos importar um cara que sabe trocar de rota por meio do react-router-dom o componente Link passando um parâmetro informando para onde esse link vai apontar <Link to="/sobre">. Para finalizarmos nosso projetinho, vamos fazer a última alteração no nosso componente:

####### ./src/index.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { Link } from 'react-router-dom'
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
<Link to="/sobre">Ir para a página sobre \o/</Link>
To get started, edit <code>src/App.js</code> and save to reload.
</p>
</div>
);
}
}
export default App;

E agora, podemos navegar entre as páginas livremente sem refresh também \o

Nossa App já esta trocando de páginas tudo certinho :)

E para quem quiser ver tudo pronto, segue aqui o código do projeto e o link com tudo funcionando aqui :)

Bom galera, espero que o post tenha ajudado, o foco realmente era só mostrar como fazer o React Router funcionar. Parece um bicho de 7 cabeças olhando a documentação à primeira vista, mas no final o crucial é entender os 3 imports que fizemos do react-router-dom, o mais chatinho é a lógica e entender onde cada componente vai pra fazer tudo funcionar.

Esse ano estou com bastante conteúdo para compartilhar e quem quiser acompanhar é só me seguir pelo Twitter ou da uma olhada no meu site pessoal :)