Criando uma Tela de Login em React: Do Formulário à Página Protegida

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

Fluxo de login: preencher os dados, validar, enviar para verificação, guardar o estado e proteger a página
Fluxo de login: preencher os dados, validar, enviar para verificação, guardar o estado e proteger a página

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.