Iniciando com ReactJS: Navegação e Autenticação com JWT
react
Esse post é a oitava parte da série de posts “Clone AirBnB com AdonisJS, React Native e ReactJS” onde iremos construir do zero uma aplicação web com ReactJS e também uma aplicação mobile com React Native com dados servidos através de uma API REST feita com NodeJS utilizando o framework AdonisJS.
  • Parte 8: Iniciando com ReactJS: Navegação e Autenticação com JWT;
Cara que insano tem sido esses posts do clone do AirBnB. O Diego chegou arrasando com o AdonisJS e o Cláudio não ficou para trás  com o React Native e agora é minha vez de contribuir com você para essa tríade ficar perfeita.
E vamos de ReactJS, então já apertou o sinto? O foguete já vai voar!!!
Obs.: Não se esqueça de rodar a API do Adonis, pois do contrário o app não conseguirá estabelecer conexão.

Iniciando

Primeiro passo é instalar o CLI (Command Line Interface) do ReactJS
npm install -g create-react-app
E criar o projeto:
create-react-app airbnb-web
Feito isso, adicione algumas dependências:
npm install react-router-dom axios styled-components prop-types font-awesome
  • Axios: Cliente HTTP usado para enviar requisições à API;
  • PropTypes: Lib para chegagem de tipo das props de componentes React;
  • ReactRouter:Lib implementação de navegação na aplicação;
  • Font Awesome: Lib de fontes de ícones.
Agora, você precisa definir uma organização na estrutura do projeto:
src/ |--- assets/ # Aqui ficará as imagens |--- configs/ # Variáveis de configurações |--- pages/ # As nossas páginas |--- services/ # Configuração de serviços utilizados |--- styles/ # Estilos globais |--- App.js # Arquivo que conterá configurações principais do App |--- index.js # Ponto de entrada para execução do nosso App |--- routes.js # Arquivo contendo as principais rotas do App
Essa será a configuração inicial e a medida que for preciso você vai alterando. Como o create-react-app cria uma estrutura com arquivos e códigos básicos, é preciso retira-los para ficar organizado como mostrei acima. No index.js:
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render(<App />, document.getElementById('root'));
E no App.js:
import React from "react"; import Routes from "./routes"; const App = () => <Routes />; export default App;

Serviços

Nesse sistema existirá dois serviços importantes que serão requisitados em diversas situações, por isso que foi criado o diretório services. Um serviço para a autenticação do usuários, o auth.js, e o outro para consumir os dados da nossa API feita com o AdonisJS, o api.js.
No arquivo services/auth.js, como dito, será tratado a autenticação dos usuários, e para isso há quatro funções loginlogoutgetToken e isAuthenticated:
export const TOKEN_KEY = "@airbnb-Token"; export const isAuthenticated = () => localStorage.getItem(TOKEN_KEY) !== null; export const getToken = () => localStorage.getItem(TOKEN_KEY); export const login = token => { localStorage.setItem(TOKEN_KEY, token); }; export const logout = () => { localStorage.removeItem(TOKEN_KEY); };
Já no services/api.js será definido qual é a API de consumo, para você não ficar passando isso por extenso toda hora, e já pode definir o headerde Authorization passando o token jwt caso o mesmo exista. (Famoso 2 coelhos, uma cajadada rsrs)
import axios from "axios"; import { getToken } from "./auth"; const api = axios.create({ baseURL: "http://127.0.0.1:3333" }); api.interceptors.request.use(async config => { const token = getToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); export default api;
Aqui foi utilizado o interceptors do Axios, como o nome sugere eleintercepta uma requisição request antes dela efetivamente acontecer, nesse instante é verificado se existe um token no localStorage, e existindo, ele adiciona o Header de Authorization na request. Isso possibilitará o acesso a páginas que precisam de autenticação, assim como o Diego configurou no Insomnia.

Rotas

Como é comum na maioria dos Apps, a navegação entre páginas, será importante neste também. Por ser pequeno, conterá três rotas principaissendo o SignInSignUp e App.
  • SignIn: Entrar com as credenciais para acessar o sistema.
  • SignUp: Criar uma nova conta para acessar o sistema.
  • App: Área que contém as properties e permite adicionar novas.
  • NotFound: Para rotas desconhecidas.
Quem vai orquestrar isso será o ReactRouter no arquivo routes.js onde montaremos a seguinte estrutura:
import React from "react"; import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom"; import { isAuthenticated } from "./services/auth"; const PrivateRoute = ({ component: Component, ...rest }) => ( <Route {...rest} render={props => isAuthenticated() ? ( <Component {...props} /> ) : ( <Redirect to={{ pathname: "/", state: { from: props.location } }} /> ) } /> ); const Routes = () => ( <BrowserRouter> <Switch> <Route exact path="/" component={() => <h1>Login</h1>} /> <Route path="/signup" component={() => <h1>SignUp</h1>} /> <PrivateRoute path="/app" component={() => <h1>App</h1>} /> <Route path="*" component={() => <h1>Page not found</h1>} /> </Switch> </BrowserRouter> ); export default Routes;
Se você não entendeu porque crio o PrivateRoute veja esse vídeo do Diego sobre controle de rotas no youtube. Dê o npm start e até o momento você terá isso:
notion image
Repara que quando acesso http://localhost:3000/app ele me retorna para a página inicial? Isso é o esperado, uma vez que o usuário ainda não está logado no sistema. E na rota http://localhost:3000/umapaginaqualquer ele exibe o Page Not  Found como o esperado também.

SignUp

Agora que você já tem a estrutura de rotas prontas, precisa definir as páginas do projeto.
Comece com com a página de SignUp. Primeiro defina o formulário para ter noção daquilo que será a página e vá, posteriormente, estilizando o que for necessário com StyledComponent.
Como estou falando de páginas, os arquivos ficarão no diretório pages e para ficar mais organizado você deve criar um diretório para cada página, sendo que o nome dele será o nome da página e conterá pelo menos o arquivo index.js com a lógica do componente e o styles.js com as estilizações.
src/pages/ |--- SignUp/ |--- index.js |--- styles.js
Primeiro, crie um componente Form e Containeratravés do StyledComponents, mas não estilize ainda. O styled disponibiliza todos os elementos que temos em HTML, e com a sintaxe tagged template string conseguimos passar o CSS estilizando o elemento selecionado.
import styled from "styled-components"; export const Container = styled.div``; export const Form = styled.form``;
A princípio será usado o elemento sem estilização, para você perceber que é um componente comum.
Em index.js ficará:
import React, { Component } from "react"; import { Link } from "react-router-dom"; import Logo from "../../assets/airbnb-logo.svg"; import { Form, Container } from "./styles"; class SignUp extends Component { state = { username: "", email: "", password: "", error: "" }; handleSignUp = e => { e.preventDefault(); alert("Eu vou te registrar"); }; render() { return ( <Container> <Form onSubmit={this.handleSignUp}> <img src={Logo} alt="Airbnb logo" /> {this.state.error && <p>{this.state.error}</p>} <input type="text" placeholder="Nome de usuário" onChange={e => this.setState({ username: e.target.value })} /> <input type="email" placeholder="Endereço de e-mail" onChange={e => this.setState({ email: e.target.value })} /> <input type="password" placeholder="Senha" onChange={e => this.setState({ password: e.target.value })} /> <button type="submit">Cadastrar grátis</button> <hr /> <Link to="/">Fazer login</Link> </Form> </Container> ); } } export default SignUp;
O arquivo para o Logo será disponibilizado no repositório, basta copia-lo para o assets.
E para finalizar adicionar a Page no routes.js:
// ... // Adicionar o import import SignUp from "./pages/SignUp"; // ... // Substituir de <Route path="/signup" component={() => <h1>SignUp</h1>} /> // Para <Route path="/signup" component={SignUp} /> // ...
Nesse momento a página estará muito feia, algo parecido com isso:
notion image
O ponto que volto a frisar é que estamos utilizando dois elementos do StyledComponent e que na prática é como se você estivesse usando a tag convencional como os demais elementos da página.
Antes que eu me esqueça, você deve definir um estilo global e para isso vamos com o StyledComponent também. Já vamos importar o font-awesome, para utiliza-lo depois. Crie um arquivo global.js no diretório src/styles que havíamos criado anteriormente, com o seguinte conteúdo:
import { injectGlobal } from "styled-components"; import "font-awesome/css/font-awesome.css"; injectGlobal` * { box-sizing: border-box; padding: 0; margin: 0; outline: 0; } body, html { background: #eee; font-family: 'Helvetica Neue', 'Helvetica', Arial, sans-serif; text-rendering: optimizeLegibility !important; -webkit-font-smoothing: antialiased !important; height: 100%; width: 100%; } `;
E fazer um kuchiyose no jutsu dele, digo importa-lo em App.js:
import React from "react"; import "./styles/global"; // ...
Bora estilizar a página então?? Volte no arquivo styles.js da Page SignUp e faça o seguinte:
import styled from "styled-components"; export const Container = styled.div` display: flex; align-items: center; justify-content: center; height: 100vh; `; export const Form = styled.form` width: 400px; background: #fff; padding: 20px; display: flex; flex-direction: column; align-items: center; img { width: 100px; margin: 10px 0 40px; } p { color: #ff3333; margin-bottom: 15px; border: 1px solid #ff3333; padding: 10px; width: 100%; text-align: center; } input { flex: 1; height: 46px; margin-bottom: 15px; padding: 0 20px; color: #777; font-size: 15px; width: 100%; border: 1px solid #ddd; &::placeholder { color: #999; } } button { color: #fff; font-size: 16px; background: #fc6963; height: 56px; border: 0; border-radius: 5px; width: 100%; } hr { margin: 20px 0; border: none; border-bottom: 1px solid #cdcdcd; width: 100%; } a { font-size: 16; font-weight: bold; color: #999; text-decoration: none; } `;
E como num passe de mágica temos:
notion image
Você percebeu que com o StyledComponent foi estilizado além dos elementos passados, os filhos dele também foram? Isso ocorreu porque os elementos foram encadeados. Funciona como o CSS normal, só que nesse caso você pode colocar os elementos filhos dentro das chaves {}poupando assim ter que escrever por extenso todas as tags do CSS e deixando isso por conta do StyledComponent. É isso mesmo, você pode usar todo o poder do CSS que apenas o elemento e os seus filhos receberão  a estilização!!!! Isso é bruto, bruto, bruto!!!

Criando usuário

Se você observou bem, a estrutura para pegar os dados do formulário está feita passando do input para o state, bem como a função que será chamada quando o botão “Cadastrar Grátis” for pressionado disparando o evento onSubmit.
Só uma ressalva, será necessário habilitar o CORS do servidor AdonisJS que temos, para isso no arquivo config/cors.js procure pela chave origin e ponha true.
O que precisa agora é fazê-la funcionar, importando a api de consumo de dados, adicionando o withRouter para ter acesso as rotas e substituindo a função handleSignUp no arquivos index.js:
// De //import { Link } from "react-router-dom"; // Para import { Link, withRouter } from "react-router-dom"; // ... import api from "../../services/api"; // ... handleSignUp = async e => { e.preventDefault(); const { username, email, password } = this.state; if (!username || !email || !password) { this.setState({ error: "Preencha todos os dados para se cadastrar" }); } else { try { await api.post("/users", { username, email, password }); this.props.history.push("/"); } catch (err) { console.log(err); this.setState({ error: "Ocorreu um erro ao registrar sua conta. T.T" }); } } }; ///... export default withRouter(SignUp);
Agora a função handleSignUp é assíncrona e por isso foi adicionado o async/await para trabalhar com isso. Os dados do state foram extraídos e feito uma verificação simples adicionando um error ao estado caso falte algum campo preenchido ou continua com o try/catch. Dentro do try foi feito uma requisição com o método POST do HTTP enviando os dados para inserir o usuário novo. Se tudo ocorrer bem você será redirecionado para a rota de login, caso contrário um erro será informado.
Só reforçando que o withRouter é um HOC que adiciona a propriedade history que possibilita mudar de página.
notion image

SignIn

Assim como antes, monte uma estrutura de diretórios para página de SignIn:
src/pages/ |--- SignIn/ |--- index.js |--- styles.js
Dessa vez, serei mais direto com os códigos, então no index.js:
import React, { Component } from "react"; import { Link, withRouter } from "react-router-dom"; import Logo from "../../assets/airbnb-logo.svg"; import api from "../../services/api"; import { login } from "../../services/auth"; import { Form, Container } from "./styles"; class SignIn extends Component { state = { email: "", password: "", error: "" }; handleSignIn = async e => { e.preventDefault(); const { email, password } = this.state; if (!email || !password) { this.setState({ error: "Preencha e-mail e senha para continuar!" }); } else { try { const response = await api.post("/sessions", { email, password }); login(response.data.token); this.props.history.push("/app"); } catch (err) { this.setState({ error: "Houve um problema com o login, verifique suas credenciais. T.T" }); } } }; render() { return ( <Container> <Form onSubmit={this.handleSignIn}> <img src={Logo} alt="Airbnb logo" /> {this.state.error && <p>{this.state.error}</p>} <input type="email" placeholder="Endereço de e-mail" onChange={e => this.setState({ email: e.target.value })} /> <input type="password" placeholder="Senha" onChange={e => this.setState({ password: e.target.value })} /> <button type="submit">Entrar</button> <hr /> <Link to="/signup">Criar conta grátis</Link> </Form> </Container> ); } } export default withRouter(SignIn);
A estrutura é semelhante a página de SignUp só que o método disparado no submit é o handleSignIn, que mostra um erro caso falte algum campo ser preenchido e uma estrutura de try/catch para a requisição na API, exibindo um erro caso haja algum problema na requisição. Dessa vez foi necessário guardar a resposta da API, pois ela traz consigo o token que é guardado no localStorage com a função login que criamos em auth.
Para estilizar mais um vez use o StyledComponents, no styles.js:
import styled from "styled-components"; export const Container = styled.div` display: flex; align-items: center; justify-content: center; height: 100vh; `; export const Form = styled.form` width: 400px; background: #fff; padding: 20px; display: flex; flex-direction: column; align-items: center; img { width: 100px; margin: 10px 0 40px; } p { color: #ff3333; margin-bottom: 15px; border: 1px solid #ff3333; padding: 10px; width: 100%; text-align: center; } input { flex: 1; height: 46px; margin-bottom: 15px; padding: 0 20px; color: #777; font-size: 15px; width: 100%; border: 1px solid #ddd; &::placeholder { color: #999; } } button { color: #fff; font-size: 16px; background: #fc6963; height: 56px; border: 0; border-radius: 5px; width: 100%; } hr { margin: 20px 0; border: none; border-bottom: 1px solid #cdcdcd; width: 100%; } a { font-size: 16; font-weight: bold; color: #999; text-decoration: none; } `;
Para finalizar, chame a  página no routes.js:
// ... // Adicionar o import import SignIn from "./pages/SignIn"; // ... // Substituir de <Route exact path="/" component={() => <h1>Login</h1>} /> // Para <Route exact path="/" component={SignIn} /> // ...
notion image

É só isso, não tem mais jeito, acabou, boa sorte…

Caaaalma só quis cantar um pouco HAHAH
Essa primeira parte foi para você criar uma conta e acessar o sistema com ela, e você já deve ter percebido que não tem muito segredo! Ainda vamos adicionar o Mapa, listar propriedades e muito mais! * — *
Esse código está no github, só clicar aqui
Ah, vale lembrar que você pode deixar seu comentário ali embaixo com suas dúvidas, sugestões e até mesmo falando o que está achando da série.
Abração, nos vemos logo!!

Aprenda programação do zero e DE GRAÇA

No Discover você vai descomplicar a programação, aprender a criar seu primeiro site com a mão na massa e iniciar sua transição de carreira.

COMECE A ESTUDAR AGORA