OmniStack11: criando a interface web com ReacJS [Dia 03]
![OmniStack11: criando a interface web com ReacJS [Dia 03]](http://abneroliveira.eti.br/wp-content/uploads/2020/03/OmniStack-11-2.png)
Vamos lá começar o terceiro dia de semana Omnistack. Daremos prosseguimento ao projeto iniciado no primeiro dia na pasta frontend
, criando a interface web com ReactJs.
Abaixo os links para todos os dias da Semana Omnistack 11
- DIA 01 – Conceitos e ambiente de desenvolvimento
- DIA 02 – Backend – Criando a base da aplicação
- DIA 03 – Frontend – Construindo a interface web
- DIA 04 – Mobile – Desenvolvimento o app mobile
- DIA 05 – Funcionalidades Avançadas
Limpando a estrutura do projeto ReactJS
Então abra o projeto com o VS Code e vamos montar a estrutura de pastas do projeto, mas primeiro vamos deletar os arquivos que não precisaremos.
Delete a lista abaixo que:
- Na pasta
src
app.css
App.test.js
index.css
logo.svg
serviceWorker.js
setupTests.js
- Na pasta
public
logo192.png
logo512.png
manifest.json
robots.txt
OK, na pasta src sobrou o App.js
e o index.js
. Agora você deve ajustar esse dois arquivos retirando as importações desnecessárias e alguns códigos não devem fazer parte projeto. Eles devem ficar assim
App.js
import React from 'react';
function App() {
return (
<h1>Hello World</h1>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Já na pasta public
o arquivo deve ficar assim
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Be The Hero</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Para visualizar se está tudo funcionando, acesse a pasta frontend
pelo terminal e rode o comando yarn start
. O servidor deve inicializar e o navegador abrir uma aba em localhost:3000
com o texto Hello World.
Estrutura de pastas da interface web com ReactJS
Se tudo funcionou vamos montar a nova estrutura. Para deixar o projeto mais fácil de atualizar crie todas as pasta e arquivos abaixo.
/frontend
/src
/assets
/services
/pages
/Logon
-index.js
-styles.css
/NewIncident
-index.js
-styles.css
/Profile
-index.js
-styles.css
/Register
-index.js
-styles.css
Na pasta assets
por padrão ficam guardados os arquivos de imagens utilizados nas interfaces, baixe o logo.svg
e o heroes.png
clicando aqui e coloque na pasta.
Cada pasta dentro da pasta pages
corresponde a uma tela. Por exemplo a página de login está dentro da pasta Logon
, que contem os arquivos index.js
e o styles.css
A pasta services conterá o arquivo do cliente HTTP que usaremos para consumir os dados do nosso backend.
Arquivo de rotas
Antes de iniciar o desenvolvimento da interface web com ReactJS vamos definir as rodas.
Crie o arquivo routes.js
na pasta src
. Importe todos os componentes (páginas), bem como o react-router-dom. Ele gerencia as rotas a partir dos componentes <BrowserRouter>
e <Switch>
.
O <BrowserRouter>
deve estar por volta de todas rotas e o <Switch>
vai garantir que apenas um rota seja acessada por vez.
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Logon from './pages/Logon';
import Register from './pages/Register';
import Profile from './pages/Profile';
import NewIncident from './pages/NewIncident';
export default function Routes() {
return(
<BrowserRouter>
<Switch>
<Route path="/" exact component={Logon} />
<Route path="/register" component={ Register } />
<Route path="/profile" component={ Profile } />
<Route path="/incidents/new" component={ NewIncident } />
</Switch>
</BrowserRouter>
);
}
O arquivo App.js
também deve ser atualizado.
import React from 'react';
import Routes from './routes';
function App() {
return (
<Routes />
);
}
export default App;
Falta somente mais um coisa para testar instalar o react-router-dom como dependência do projeto.
yarn add react-router-dom
Agora vamos começar o desenvolvimento da interface web com ReactJS.
Iniciando o layout da interface web com ReactJS
Um padrão utilizado no desenvolvimento de sistemas web é o reuso de código, pensando nessa perspectiva, é importante ter o máximo de componentes de interface em comum a todas a páginas definidas em um único arquivo.
Dessa forma é possível importar essas configurações em qualquer página do projeto, facilitando a manutenção do código.
Sendo assim crie na pasta src
o arquivo global.css
. Ele deve conter o fontes, modelos de input, botão e links comuns a todas as interfaces do projeto.
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap');
* {
margin: 0;
padding: 0;
outline: 0;
box-sizing: border-box;
}
body {
font: 400 14px Roboto, sans-serif;
background: #f0f0f5;
-webkit-font-smoothing: antialiased;
}
input, button, textarea {
font: 400 18px Roboto, sans-serif;
}
button {
cursor: pointer;
}
form input {
width: 100%;
height: 60px;
color: #333;
border: 1px solid #dcdce6;
border-radius: 8px;
padding: 0 24px;
}
form textarea {
width: 100%;
resize: vertical;
min-height: 140px;
height: 60px;
color: #333;
border: 1px solid #dcdce6;
border-radius: 8px;
padding: 16px 24px;
line-height: 24px;
}
.button {
width: 100%;
height: 60px;
background: #e02041;
border: 0;
border-radius: 8px;
color: #fff;
font-weight: 700;
margin-top: 16px;
display: inline-block;
text-align: center;
text-decoration: none;
font-size: 18px;
line-height: 60px;
transition: filter 0.2s;
}
.button:hove r {
filter: brightness(90%);
}
.back-link {
display: flex;
align-items: center;
margin-top: 40px;
color: #41414d;
font-size: 18px;
text-decoration: none;
font-weight: 500;
transition: opacity 0.2s;
}
.back-link {
margin-right: 8px;
}
.back-link {
opacity: 0.8;
}
Não esqueça de importar o global.css no App.css. Insira a linha abaixo no arquivo.
import './global.css';
Desenvolvendo a tela de login

Essa imagem acima é a tela de login. Olhando por cima, temos um container, logo, um label no form, um input, um botão e um link para a página de cadastro.
import React from 'react';
import { FiLogIn } from 'react-icons/fi';
import { Link, useHistory } from 'react-router-dom';
import './styles.css';
import heroesImg from '../../assets/heroes.png';
import logo from '../../assets/logo.svg';
export default function Logon() {
return (
<div className="logonContainer">
<section className="form">
<img src={logo} alt="Be the hero"/>
<form >
<h1>Faça o seu logon</h1>
<input placeholder="Sua ID" />
<button className="button" type="submit">Entrar</button>
<Link className="back-link" to="/register">
<FiLogIn size={16} color="#E02041" />
Não tenho cadastro
</Link>
</form>
</section>
<img src={heroesImg} alt="Heroes"/>
</div>
);
}
Das dependências do login apenas o react-icons ainda não foi instalado no projeto. Use o comando abaixo para instalar
yarn add react-icons
Se você for testar ainda vai estar faltando a folha de estilos – styles.css
.logonContainer {
width: 100%;
max-width: 1120px;
height: 100vh;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
}
.logonContainer section.form {
width: 100%;
max-width: 350px;
margin-right: 30px;
}
.logonContainer section.form form {
margin-top: 100px;
}
.logonContainer section.form form h1{
font-size: 32px;
margin-bottom: 32px;
}
Agora test com yarn start
em localhost:3000 deve aparecer uma tela igual a do início da seção.
Bora que tá ficando bom. Vamos continuar criando a interface web com ReacJS.
Registro de ONGs
A página de registro de ONGs é bem parecida com a tela de login. O cadastro de ONGs tem o nome, email, whatsapp, cidade e estado da ONG.
import React, { useState } from 'react';
import { FiArrowLeft } from 'react-icons/fi';
import { Link, useHistory } from 'react-router-dom';
import './styles.css';
import logoImg from '../../assets/logo.svg';
export default function Register() {
return(
<div className="register-container">
<div className="content">
<section>
<img src={logoImg} alt="Be Ther Hero" />
<h1>Cadastro</h1>
<p>Faça seu cadastro, entre na plataforma e ajude pessoas a encotrarem os casos da sua ONG</p>
<Link className="back-link" to="/">
<FiArrowLeft size={16} color="#E02041"/>
Já tenho cadastro
</Link>
</section>
<form>
<input placeholder="Nome da ONG" />
<input type="email" placeholder="E-mail" />
<input placeholder="Whatsapp" />
<div className="input-group">
<input placeholder="Cidade" />
<input placeholder="UF" style= {{ width: 80 }}/>
</div>
<button className="button" type="submit">Cadastrar</button>
</form>
</div>
</div>
);
}
O style.css
deve ficar assim
.register-container {
width: 100%;
max-width: 1120px;
height: 100vh;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
}
.register-container .content {
width: 100%;
padding: 96px;
background: #f0f0f5;
box-shadow: 0 0 100px rgba(0, 0,0, 0.1);
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.register-container .content section {
width: 100%;
max-width: 380px;
}
.register-container .content section h1 {
margin: 64px 0 32px;
font-size: 32px;
}
.register-container .content section p {
font-size: 18px;
color: #737380;
line-height: 32px;
}
.register-container .content form {
width: 100%;
max-width: 450px;
}
.register-container .content form input {
margin-top: 8px;
}
.register-container .content form .input-group {
display: flex;
}
.register-container .content form .input-group input + input {
margin-left: 8px;
}
Lista de Casos específicos de uma ONG
Essa lista é composta por cards, em 2 colunas, com os casos da ONG logada.

Código do componente Reactjs
import React, { useState , useEffect } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { FiPower, FiTrash2 } from 'react-icons/fi';
import './styles.css';
import logoImg from '../../assets/logo.svg';
export default function Profile() {
return(
<div className="profile-container">
<header>
<img src={ logoImg } alt="Be the hero" />
<span>Bem vindo, ONG Tal</span>
<Link className="button" to="/incidents/new">Cadastrar novo caso</Link>
<button type="button" onClick="">
<FiPower size={18} color="#E02041" />
</button>
</header>
<h1>Casos cadastrados</h1>
<ul>
<li>
<strong>CASO: </strong>
<p>título</p>
<strong>DESCRICAO: </strong>
<p>descrição ...</p>
<strong>Valor: </strong>
<p>R$ 199.00</p>
<button onClick="" type="button">
<FiTrash2 size={20} color="#a8a8b3"></FiTrash2>
</button>
</li>
<li>
<strong>CASO: </strong>
<p>título</p>
<strong>DESCRICAO: </strong>
<p>descrição ...</p>
<strong>Valor: </strong>
<p>R$ 199.00</p>
<button onClick="" type="button">
<FiTrash2 size={20} color="#a8a8b3"></FiTrash2>
</button>
</li>
<li>
<strong>CASO: </strong>
<p>título</p>
<strong>DESCRICAO: </strong>
<p>descrição ...</p>
<strong>Valor: </strong>
<p>R$ 199.00</p>
<button onClick="" type="button">
<FiTrash2 size={20} color="#a8a8b3"></FiTrash2>
</button>
</li>
<li>
<strong>CASO: </strong>
<p>título</p>
<strong>DESCRICAO: </strong>
<p>descrição ...</p>
<strong>Valor: </strong>
<p>R$ 199.00</p>
<button onClick="" type="button">
<FiTrash2 size={20} color="#a8a8b3"></FiTrash2>
</button>
</li>
</ul>
</div>
);
}
CSS
.profile-container {
width: 100%;
max-width: 1180px;
padding: 0 30px;
margin: 32px auto;
}
.profile-container header {
display: flex;
align-items: center;
}
.profile-container header span {
font-size: 20px;
margin-left: 24px;
}
.profile-container header img {
height: 64px;
}
.profile-container header a {
width: 260px;
margin-left: auto;
margin-top: 0;
}
.profile-container header button {
height: 60px;
width: 60px;
border-radius: 4px;
border: 1px solid #dcdce6;
background: transparent;
margin-left: 16px;
transition: border-color 0.2s;
}
.profile-container header button:hover {
border-color: #999;
}
.profile-container h1 {
margin-top: 80px;
margin-bottom: 24px;
}
.profile-container ul {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 24px;
list-style: none;
}
.profile-container ul li {
background: #FFF;
padding: 24px;
border-radius: 8px;
position: relative;
}
.profile-container ul li button {
position: absolute;
right: 24px;
top: 24px;
border: 0;
}
.profile-container ul li button:hover {
opacity: 0.8;
}
.profile-container ul li strong {
display: block;
margin-bottom: 16px;
color: #41414d;
}
.profile-container ul li p + strong {
margin-top: 32px;
}
.profile-container ul li p {
color: #737380;
line-height: 21px;
font-size: 16px;
}
Interface web de cadastro de caso.

A ONG logada pode cadastrar seus casos para contato futuro pelos usuários app mobile, que será desenvolvido no próximo dia da Omnistack11.
Continue criando a interface web com o Reactjs
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import logoImg from '../../assets/logo.svg';
import { FiArrowLeft } from 'react-icons/fi';
import './styles.css';
export default function NewIncident() {
return(
<div className="new-incident-container">
<div className="content">
<section>
<img src={logoImg} alt="Be Ther Hero" />
<h1>Cadastro novo caso</h1>
<p>Descrever o caso detalhadamente para encontrar um herói para resolver isso.</p>
<Link className="back-link" to="/profile">
<FiArrowLeft size={16} color="#E02041"/>
Voltar para home
</Link>
</section>
<form onSubmit ="">
<input
placeholder="Titulo do caso"
value=""
onChange=""/>
<textarea
placeholder="Descrição"
value=""
onChange=""
/>
<input
placeholder="Valor em reais"
value=""
onChange =""/>
<button className="button" type="submit">Registrar</button>
</form>
</div>
</div>
);
}
CSS da tela registro de caso
.new-incident-container {
width: 100%;
max-width: 1120px;
height: 100vh;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
}
.new-incident-container .content {
width: 100%;
padding: 96px;
background: #f0f0f5;
box-shadow: 0 0 100px rgba(0, 0,0, 0.1);
border-radius: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.new-incident-container .content section {
width: 100%;
max-width: 380px;
}
.new-incident-container .content section h1 {
margin: 64px 0 32pxnew-incident-container2px;
}
.new-incident-container .content section p {
color: #737380;
line-height: 32px;
}
.register-container .content form {
width: 100%;
max-width: 450px;
}
.new-incident-container .content form input,
.new-incident-container .content form textarea {
margin-top: 8px;
}
Bem com esse último arquivo .css a esqueleto da aplicação ReactJS foi concluída.
No terminal digite yarn start
e acesse localhost:3000/
para acessar o login.
Para acessar as outras rotas insira o recurso correspondente a tela que você deseja ver
Continuando o desenvolvimento do frontend com Reactjs
O precisamos fazer agora é configurar o Aaxios para consumir os dados do backend.
Instale o Axios pelo terminal pelo comando yarn add axios
e crie dentro da pasta services o arquivo api.js.
Esse arquivo vai fazer a função de cliente HTTP. Insira o conteúdo abaixo no arquivo.
Lá você define de qual domínio que seu serviço vai consumir. No nosso caso é o localhost:3333. Já que o backend tá rodando na mesma máquina.
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:3333',
})
export default api;
Consumir dados do backend
Primeiramente é preciso atualizar todos os componentes importando o axios com a linha import api from '../../services/api';
.
Login com LocalStorage
O login vai precisar da importação do axios
e do useHistory
do react-router-dom. O usuário para autenticar deve inserir o código da ONG no input e clicar em ENTRAR.
Você tem que criar uma função acessar a rota/sessions
do backend que vai dizer se aquele código é válido ou não. Se não for emite um alerta de código inválido. Se corresponder você salvar o id
no localStorage pois as outras rotas precisarão para cadastrar ou listar o casos de um ONG.
Você também deve salvar o nome da ONG no localStorage. A lista de casos precisa dessa inforamção par mostrar o nome da ONG na tela.
import React, { useState } from 'react';
import './styles.css';
import { FiLogIn } from 'react-icons/fi';
import { Link, useHistory } from 'react-router-dom';
import api from '../../services/api';
import logo from '../../assets/logo.svg';
import heroesImg from '../../assets/heroes.png';
export default function Logon() {
const [id, setId] = useState('');
const history = useHistory();
async function handleLogin(e) {
e.preventDefault();
try {
console.log(id);
const response = await api.post('sessions', { id });
localStorage.setItem('ongId', id);
localStorage.setItem('ongName',response.data.name);
history.push('/profile');
} catch {
alert('Falha no login, tente novamente!');
}
}
return(
<div className="logonContainer">
<section className="form">
<img src={logo} alt="Be the hero"/>
<form onSubmit={handleLogin}>
<h1>Faça o seu logon</h1>
<input
placeholder="Sua ID"
value={id}
onChange= {e => setId(e.target.value) }/>
<button className="button" type="submit">Entrar</button>
<Link className="back-link" to="/register">
<FiLogIn size={16} color="#E02041"/>
Não tenho cadastro
</Link>
</form>
</section>
<img src={heroesImg} alt="Heroes"/>
</div>
);
}
Cadastrando de ONG
Com o useState você poder carregar os valores dos campos do formulário e depois usar o axios para passar para a API, que por fim salvará em banco de dados.
import React, { useState } from 'react';
import { FiArrowLeft } from 'react-icons/fi';
import { Link, useHistory } from 'react-router-dom';
import api from '../../services/api';
import './styles.css';
import logoImg from '../../assets/logo.svg';
export default function Register() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [whatsapp, setWhatsapp] = useState('');
const [city, setCity] = useState('');
const [uf, setUf] = useState('');
const history = useHistory();
async function handleRegister(e) {
e.preventDefault();
const data = {
name,
email,
whatsapp,
city,
uf,
};
try {
const response = await api.post('ongs', data);
alert(`Seu ID de acesso: ${response.data.id}`);
history.push('/');
} catch {
alert(`Erro no cadastro, tente novamente`);
}
}
return(
<div className="register-container">
<div className="content">
<section>
<img src={logoImg} alt="Be Ther Hero" />
<h1>Cadastro</h1>
<p>Faça seu cadastro, entre na plataforma e ajude pessoas a encotrarem os casos da sua ONG</p>
<Link className="back-link" to="/">
<FiArrowLeft size={16} color="#E02041"/>
Não tenho cadastro
</Link>
</section>
<form onSubmit={handleRegister}>
<input
placeholder="Nome da ONG"
value={name}
onChange={e => setName(e.target.value)}/>
<input
type="email"
placeholder="E-mail"
value={email}
onChange={e => setEmail(e.target.value)}/>
<input
placeholder="Whatsapp"
value={whatsapp}
onChange={e => setWhatsapp(e.target.value)}/>
<div className="input-group">
<input
placeholder="Cidade"
value={city}
onChange={e => setCity(e.target.value)}/>
<input
placeholder="UF"
style= {{ width: 80 }}
value={uf}
onChange={e => setUf(e.target.value)}/>
</div>
<button className="button" type="submit">Cadastrar</button>
</form>
</div>
</div>
);
}
Cadastro de um novo caso
O cadastro de um novo caso é bem parecido com o cadastro de ONGs a diferença é que o backend solicita o código da ONG logada que está salvo no localStorage, portanto você carregar essa informação e preencher o valor antes de fazer o insert com o axios.
import React, { useState } from 'react';
import { Link, useHistory } from 'react-router-dom';
import logoImg from '../../assets/logo.svg';
import { FiArrowLeft } from 'react-icons/fi';
import api from '../../services/api';
import './styles.css';
export default function NewIncident() {
const [title, setTitle] = useState('');
const [description,setDescription] = useState('');
const [value, setValue] = useState('');
const history = useHistory();
const ongId = localStorage.getItem('ongId');
async function handleSubmit(e) {
e.preventDefault();
const data = {
title,
description,
value,
}
try {
await api.post('incidents', data, {
headers: {
Authorization: ongId,
}
})
history.push('/profile');
} catch {
alert('Erro ao cadastrar caso, tente novamente');
}
}
return(
<div className="new-incident-container">
<div className="content">
<section>
<img src={logoImg} alt="Be Ther Hero" />
<h1>Cadastro novo caso</h1>
<p>Descrever o caso detalhadamente para encontrar um herói para resolver isso.</p>
<Link className="back-link" to="/profile">
<FiArrowLeft size={16} color="#E02041"/>
Voltar para home
</Link>
</section>
<form onSubmit = {handleSubmit}>
<input
placeholder="Titulo do caso"
value= { title }
onChange= {e => setTitle(e.target.value)}/>
<textarea
placeholder="Descrição"
value={ description }
onChange= {e => setDescription(e.target.value)}
/>
<input
placeholder="Valor em reais"
value={value}
onChange = {e => setValue(e.target.value)}/>
<button className="button" type="submit">Registrar</button>
</form>
</div>
</div>
);
}
Consumindo lista de casos do backend
A lista também precisa do ongId que está no localStorage para filtrar o os casos somente da ONG logada.
import React, { useState , useEffect } from 'react';
import { Link, useHistory } from 'react-router-dom';
import { FiPower, FiTrash2 } from 'react-icons/fi';
import api from '../../services/api';
import './styles.css';
import logoImg from '../../assets/logo.svg';
export default function Profile() {
const [incidents, setIncidents] = useState([]);
const history = useHistory();
const ongId = localStorage.getItem('ongId');
const ongName = localStorage.getItem('ongName');
useEffect(() => {
api.get('profile', {
headers: {
Authorization: ongId,
}
}).then(response => {
setIncidents(response.data);
})
} ,[ongId]);
async function handleDeleteIncident(id) {
try {
await api.delete(`incidents/${id}`, {
headers: {
Authorization: ongId,
}
});
setIncidents(incidents.filter(incident => incident.id !== id));
} catch {
alert('Erro ao deletar caso, tente novamente');
}
}
function handleLogout() {
localStorage.clear();
history.push('/');
}
return(
<div className="profile-container">
<header>
<img src={ logoImg } alt="Be the hero" />
<span>Bem vindo, {ongName}</span>
<Link className="button" to="/incidents/new">Cadastrar novo caso</Link>
<button type="button" onClick={handleLogout}>
<FiPower size={18} color="#E02041" />
</button>
</header>
<h1>Casos cadastrados</h1>
<ul>
{incidents.map(incident => (
<li key={incident.id}>
<strong>CASO: </strong>
<p>{incident.title}</p>
<strong>DESCRICAO: </strong>
<p>{incident.description}</p>
<strong>Valor: </strong>
<p>{Intl.NumberFormat('pt-Br',{style: 'currency', currency: 'BRL'}).format(incident.value)}</p>
<button onClick={() => handleDeleteIncident(incident.id)} type="button">
<FiTrash2 size={20} color="#a8a8b3"></FiTrash2>
</button>
</li>
))}
</ul>
</div>
);
}
Conseguimos finalizar mais esse dia com sucesso!
O código integral está disponível no GitHub!
[…] DIA 03 – Frontend – Construindo a interface web […]