Criando uma Tela de Login em React: Do Formulário à Página Protegida
A tela de login é uma das partes mais comuns de qualquer site. Este artigo a constrói em React passo a passo: estrutura do formulário, inputs controlados, validação, login simulado, bloqueio após várias falhas, estado do usuário compartilhado com Context e uma página protegida que esconde o conteúdo de quem não entrou. Cada exemplo já foi executado, e a saída mostrada é a saída real.
O que você precisa
Você precisa de Node.js 18 ou mais recente e de conhecimento básico de React. Os exemplos usam React 18.3.1 e são executados com o testador deste artigo, que clica em botões em um navegador simulado com jsdom. Importante: verificar a senha no navegador serve apenas para aprender. Um sistema real precisa verificar a senha no servidor, guardá-la como hash e usar HTTPS.
Ilustração
Exemplo 1: Estrutura do formulário de login
Comece com um formulário que tem campo de e-mail, campo de senha e botão de envio. O renderToStaticMarkup mostra o HTML gerado pelo React.
import React from "react";
export function App() {
return (
<form>
<h2>Entrar</h2>
<label>E-mail<input type="email" name="email" /></label>
<label>Senha<input type="password" name="password" /></label>
<button type="submit">Entrar</button>
</form>
);
}
Saída real
<form><h2>Entrar</h2><label>E-mail<input type="email" name="email"/></label><label>Senha<input type="password" name="password"/></label><button type="submit">Entrar</button></form>
Exemplo 2: Mostrar ou ocultar a senha
Guarde o estado de exibição no state e troque o tipo do input entre password e text quando o botão for clicado.
1. Clique no botão de mostrar senha uma vez, de desativado para ativado
import React, { useState } from "react";
export function App() {
const [show, setShow] = useState(false);
return (
<div>
<input type={show ? "text" : "password"} value="secret1" readOnly />
<button id="toggle" onClick={() => setShow(!show)}>Mostrar senha</button>
<p data-out>Mostrar senha: {show ? "Ativado" : "Desativado"}</p>
</div>
);
}
export const steps = ["#toggle"];
Saída real
Texto inicial: Mostrar senha: Desativado
Após o 1º clique: Mostrar senha: Ativado
Exemplo 3: Validação de campos
A função validate devolve uma lista de mensagens de erro. Mostre essas mensagens somente depois que o usuário enviar, para que o formulário não reclame enquanto a pessoa ainda está digitando.
import React, { useState } from "react";
// Regras de validação: devolve uma lista de mensagens de aviso. Se não houver erros, o resultado é um array vazio
export function validate(email, password) {
const errors = [];
if (!email) errors.push("Preencha o e-mail");
else if (!/^\S+@\S+\.\S+$/.test(email)) errors.push("Formato de e-mail inválido");
if (password.length < 6) errors.push("A senha deve ter no mínimo 6 caracteres");
return errors;
}
export function App() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [submitted, setSubmitted] = useState(false);
const errors = validate(email, password);
return (
<div>
<input value={email} onChange={e => setEmail(e.target.value)} />
<input value={password} onChange={e => setPassword(e.target.value)} />
<button id="submit" onClick={() => setSubmitted(true)}>Enviar formulário</button>
<p data-out>{!submitted ? "Formulário ainda não enviado" : errors.length ? errors.join(" | ") : "Nenhum erro"}</p>
</div>
);
}
export const steps = ["#submit"];
Saída real
Texto inicial: Formulário ainda não enviado
Após o 1º clique: Preencha o e-mail | A senha deve ter no mínimo 6 caracteres
Exemplo 4: Login simulado
Compare o e-mail e a senha com dados de exemplo e mostre uma mensagem de sucesso ou de conta não encontrada. Este exemplo começa com valores corretos.
import React, { useState } from "react";
// Exemplo de banco de usuários simulado. Em um sistema real, a verificação deve ser feita somente no servidor
const USERS = { "[email protected]": "secret1" };
export function login(email, password) {
if (USERS[email] === undefined) return { ok: false, message: "Esta conta não foi encontrada" };
if (USERS[email] !== password) return { ok: false, message: "Senha inválida" };
return { ok: true, message: "Login realizado: " + email };
}
export function App() {
const [email, setEmail] = useState("[email protected]");
const [password, setPassword] = useState("secret1");
const [result, setResult] = useState("Ainda não tentou entrar");
return (
<div>
<input value={email} onChange={e => setEmail(e.target.value)} />
<input value={password} onChange={e => setPassword(e.target.value)} />
<button id="submit" onClick={() => setResult(login(email, password).message)}>Entrar</button>
<p data-out>{result}</p>
</div>
);
}
export const steps = ["#submit"];
Saída real
Texto inicial: Ainda não tentou entrar
Após o 1º clique: Login realizado: [email protected]
Exemplo 5: Bloqueio após falhas repetidas
Conte as senhas erradas e desative o botão quando o limite for atingido, para desacelerar tentativas repetidas de adivinhação.
import React, { useState } from "react";
const MAX_TRIES = 3;
export function App() {
const [password, setPassword] = useState("wrong");
const [tries, setTries] = useState(0);
const [message, setMessage] = useState("");
const locked = tries >= MAX_TRIES;
function submit() {
if (locked) return;
if (password === "secret1") {
setMessage("Login realizado");
return;
}
const next = tries + 1;
setTries(next);
setMessage(next >= MAX_TRIES ? "Bloqueado temporariamente, tente novamente mais tarde" : `Senha inválida (tentativa ${next} de ${MAX_TRIES})`);
}
return (
<div>
<input value={password} onChange={e => setPassword(e.target.value)} />
<button id="submit" disabled={locked} onClick={submit}>Entrar</button>
<p data-out>{message || "Pronto para uso"}</p>
</div>
);
}
export const steps = ["#submit", "#submit", "#submit"];
Saída real
Texto inicial: Pronto para uso
Após o 1º clique: Senha inválida (tentativa 1 de 3)
Após o 2º clique: Senha inválida (tentativa 2 de 3)
Após o 3º clique: Bloqueado temporariamente, tente novamente mais tarde
Exemplo 6: Opção lembrar de mim
Uma caixa de seleção controlada pelo state, com o status mostrado diretamente ao usuário.
import React, { useState } from "react";
export function App() {
const [remember, setRemember] = useState(false);
return (
<div>
<label>
<input id="remember" type="checkbox" checked={remember} onChange={e => setRemember(e.target.checked)} />
Lembrar de mim
</label>
<p data-out>Lembrar de mim: {remember ? "Ativado" : "Desativado"}</p>
</div>
);
}
export const steps = ["#remember"];
Saída real
Texto inicial: Lembrar de mim: Desativado
Após o 1º clique: Lembrar de mim: Ativado
Exemplo 7: Guardar o estado do usuário com Context
Crie um AuthContext para que a barra de status e seus botões acessem os dados do usuário sem passar props por vários níveis.
import React, { createContext, useContext, useState } from "react";
// Guarda o estado do usuário no Context para que qualquer parte da página acesse sem passar props em camadas
const AuthContext = createContext(null);
function StatusBar() {
const { user, login, logout } = useContext(AuthContext);
return (
<div>
<p data-out>Status: {user ? "Olá, " + user : "Não conectado"}</p>
{user
? <button id="logout" onClick={logout}>Sair</button>
: <button id="login" onClick={() => login("Budi")}>Entrar</button>}
</div>
);
}
export function App() {
const [user, setUser] = useState(null);
const value = { user, login: setUser, logout: () => setUser(null) };
return (
<AuthContext.Provider value={value}>
<StatusBar />
</AuthContext.Provider>
);
}
export const steps = ["#login", "#logout"];
Saída real
Texto inicial: Status: Não conectado
Após o 1º clique: Status: Olá, Budi
Após o 2º clique: Status: Não conectado
Exemplo 8: Página que exige login
Se não houver usuário, a página pede que o visitante entre. Se houver, a página mostra o painel. Essa é a ideia por trás das rotas protegidas.
1. Entre e depois saia
import React, { useState } from "react";
// Página que só pode ser vista depois do login. Sem usuário, mostra a mensagem pedindo para entrar primeiro
function Dashboard({ user }) {
if (!user) return <p data-out>Faça login primeiro</p>;
return <p data-out>Página do painel de {user}</p>;
}
export function App() {
const [user, setUser] = useState(null);
return (
<div>
<button id="login" onClick={() => setUser("Budi")}>Entrar</button>
<button id="logout" onClick={() => setUser(null)}>Sair</button>
<Dashboard user={user} />
</div>
);
}
export const steps = ["#login", "#logout"];
Saída real
Texto inicial: Faça login primeiro
Após o 1º clique: Página do painel de Budi
Após o 2º clique: Faça login primeiro
Exemplo 9: Tela de login completa
A página final junta formulário, campos com o autocomplete correto, opção lembrar de mim e link para recuperar a senha.
import React from "react";
// Tela de login completa que reúne todas as partes: formulário, campos, opção lembrar de mim e link de recuperação de senha
export function App() {
return (
<main>
<h1>Entrar</h1>
<form>
<label>E-mail<input type="email" name="email" autoComplete="username" /></label>
<label>Senha<input type="password" name="password" autoComplete="current-password" /></label>
<label><input type="checkbox" name="remember" /> Lembrar de mim</label>
<button type="submit">Entrar</button>
</form>
<a href="/forgot">Esqueceu a senha?</a>
</main>
);
}
Saída real
<main><h1>Entrar</h1><form><label>E-mail<input type="email" name="email" autoComplete="username"/></label><label>Senha<input type="password" name="password" autoComplete="current-password"/></label><label><input type="checkbox" name="remember"/> Lembrar de mim</label><button type="submit">Entrar</button></form><a href="/forgot">Esqueceu a senha?</a></main>
Próximos passos
Depois, conecte a tela de login a uma API real com fetch, use um cookie HTTP-only ou um token seguro e adicione um fluxo de recuperação de senha que envia um link de redefinição por e-mail.
Resumo
Uma boa tela de login combina um formulário controlado por state, validação, limite para falhas repetidas, estado do usuário compartilhado com Context e uma página que confere o acesso antes de mostrar dados. Um sistema real sempre precisa verificar o acesso no servidor.