Formulários Controlados

Módulo 4: Eventos e Formulários

Aula 2
1

Componentes Controlados vs Não Controlados

20:00

Entenda a diferença e quando usar cada abordagem

2

Tipos de Inputs Controlados

Implemente diferentes tipos de campos de formulário

Tipos de Inputs Controlados

Input de Texto

function FormularioTexto() {
  const [formData, setFormData] = useState({
    nome: '',
    email: '',
    telefone: '',
    senha: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  return (
    <form>
      <input
        type="text"
        name="nome"
        value={formData.nome}
        onChange={handleChange}
        placeholder="Nome completo"
      />
      
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
      />
      
      <input
        type="tel"
        name="telefone"
        value={formData.telefone}
        onChange={handleChange}
        placeholder="Telefone"
      />
      
      <input
        type="password"
        name="senha"
        value={formData.senha}
        onChange={handleChange}
        placeholder="Senha"
      />
    </form>
  );
}

Textarea

function FormularioComentario() {
  const [comentario, setComentario] = useState('');
  const caracteresRestantes = 500 - comentario.length;

  return (
    <div>
      <textarea
        value={comentario}
        onChange={(e) => setComentario(e.target.value)}
        placeholder="Deixe seu comentário..."
        rows="5"
        cols="50"
        maxLength="500"
      />
      <p>Caracteres restantes: {caracteresRestantes}</p>
    </div>
  );
}

Select

function FormularioSelect() {
  const [pais, setPais] = useState('');
  const [estado, setEstado] = useState('');

  const estados = {
    brasil: ['SP', 'RJ', 'MG', 'BA'],
    eua: ['NY', 'CA', 'TX', 'FL'],
    canada: ['ON', 'QC', 'BC', 'AB']
  };

  return (
    <form>
      <select value={pais} onChange={(e) => setPais(e.target.value)}>
        <option value="">Selecione um país</option>
        <option value="brasil">Brasil</option>
        <option value="eua">Estados Unidos</option>
        <option value="canada">Canadá</option>
      </select>

      {pais && (
        <select value={estado} onChange={(e) => setEstado(e.target.value)}>
          <option value="">Selecione um estado</option>
          {estados[pais].map(est => (
            <option key={est} value={est}>{est}</option>
          ))}
        </select>
      )}
    </form>
  );
}

Radio Buttons

function FormularioRadio() {
  const [plano, setPlano] = useState('basico');
  const [pagamento, setPagamento] = useState('mensal');

  const planos = [
    { id: 'basico', nome: 'Básico', preco: 29.90 },
    { id: 'pro', nome: 'Pro', preco: 59.90 },
    { id: 'empresa', nome: 'Empresa', preco: 99.90 }
  ];

  return (
    <form>
      <fieldset>
        <legend>Escolha seu plano:</legend>
        {planos.map(p => (
          <label key={p.id}>
            <input
              type="radio"
              name="plano"
              value={p.id}
              checked={plano === p.id}
              onChange={(e) => setPlano(e.target.value)}
            />
            {p.nome} - R$ {p.preco}/mês
          </label>
        ))}
      </fieldset>

      <fieldset>
        <legend>Forma de pagamento:</legend>
        <label>
          <input
            type="radio"
            name="pagamento"
            value="mensal"
            checked={pagamento === 'mensal'}
            onChange={(e) => setPagamento(e.target.value)}
          />
          Mensal
        </label>
        <label>
          <input
            type="radio"
            name="pagamento"
            value="anual"
            checked={pagamento === 'anual'}
            onChange={(e) => setPagamento(e.target.value)}
          />
          Anual (20% desconto)
        </label>
      </fieldset>
    </form>
  );
}

Checkboxes

Checkbox Único

function FormularioCheckbox() {
  const [aceiteTermos, setAceiteTermos] = useState(false);
  const [receberEmails, setReceberEmails] = useState(false);

  return (
    <form>
      <label>
        <input
          type="checkbox"
          checked={aceiteTermos}
          onChange={(e) => setAceiteTermos(e.target.checked)}
        />
        Aceito os termos e condições
      </label>

      <label>
        <input
          type="checkbox"
          checked={receberEmails}
          onChange={(e) => setReceberEmails(e.target.checked)}
        />
        Desejo receber emails promocionais
      </label>
    </form>
  );
}

Múltiplos Checkboxes

function FormularioInteresses() {
  const [interesses, setInteresses] = useState([]);

  const opcoes = [
    'JavaScript',
    'React',
    'Node.js',
    'Python',
    'Machine Learning',
    'DevOps'
  ];

  const handleCheckboxChange = (opcao) => {
    setInteresses(prev => {
      if (prev.includes(opcao)) {
        return prev.filter(item => item !== opcao);
      } else {
        return [...prev, opcao];
      }
    });
  };

  return (
    <form>
      <h3>Selecione seus interesses:</h3>
      {opcoes.map(opcao => (
        <label key={opcao}>
          <input
            type="checkbox"
            checked={interesses.includes(opcao)}
            onChange={() => handleCheckboxChange(opcao)}
          />
          {opcao}
        </label>
      ))}
      <p>Selecionados: {interesses.join(', ') || 'Nenhum'}</p>
    </form>
  );
}

Range Slider

function FormularioRange() {
  const [idade, setIdade] = useState(25);
  const [experiencia, setExperiencia] = useState(5);
  const [salario, setSalario] = useState(5000);

  return (
    <form>
      <label>
        Idade: {idade} anos
        <input
          type="range"
          min="18"
          max="65"
          value={idade}
          onChange={(e) => setIdade(e.target.value)}
        />
      </label>

      <label>
        Experiência: {experiencia} anos
        <input
          type="range"
          min="0"
          max="30"
          value={experiencia}
          onChange={(e) => setExperiencia(e.target.value)}
        />
      </label>

      <label>
        Salário esperado: R$ {salario}
        <input
          type="range"
          min="1000"
          max="20000"
          step="500"
          value={salario}
          onChange={(e) => setSalario(e.target.value)}
        />
      </label>
    </form>
  );
}

Date e Time

function FormularioDataHora() {
  const [evento, setEvento] = useState({
    data: '',
    hora: '',
    dataHora: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setEvento(prev => ({
      ...prev,
      [name]: value
    }));
  };

  return (
    <form>
      <label>
        Data do evento:
        <input
          type="date"
          name="data"
          value={evento.data}
          onChange={handleChange}
          min={new Date().toISOString().split('T')[0]}
        />
      </label>

      <label>
        Hora do evento:
        <input
          type="time"
          name="hora"
          value={evento.hora}
          onChange={handleChange}
        />
      </label>

      <label>
        Data e hora completa:
        <input
          type="datetime-local"
          name="dataHora"
          value={evento.dataHora}
          onChange={handleChange}
        />
      </label>
    </form>
  );
}

Color Picker

function FormularioCor() {
  const [cores, setCores] = useState({
    primaria: '#1976d2',
    secundaria: '#dc004e',
    fundo: '#ffffff'
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setCores(prev => ({
      ...prev,
      [name]: value
    }));
  };

  return (
    <div>
      <form>
        <label>
          Cor primária:
          <input
            type="color"
            name="primaria"
            value={cores.primaria}
            onChange={handleChange}
          />
        </label>

        <label>
          Cor secundária:
          <input
            type="color"
            name="secundaria"
            value={cores.secundaria}
            onChange={handleChange}
          />
        </label>

        <label>
          Cor de fundo:
          <input
            type="color"
            name="fundo"
            value={cores.fundo}
            onChange={handleChange}
          />
        </label>
      </form>

      <div style={{
        backgroundColor: cores.fundo,
        padding: '20px',
        marginTop: '20px'
      }}>
        <h2 style={{ color: cores.primaria }}>Título Principal</h2>
        <p style={{ color: cores.secundaria }}>Texto secundário</p>
      </div>
    </div>
  );
}

Formatação de Inputs

function FormularioFormatado() {
  const [telefone, setTelefone] = useState('');
  const [cpf, setCpf] = useState('');
  const [cartao, setCartao] = useState('');

  const formatarTelefone = (valor) => {
    const numero = valor.replace(/\D/g, '');
    if (numero.length <= 11) {
      const formatado = numero.replace(
        /(\d{2})(\d{5})(\d{4})/,
        '($1) $2-$3'
      );
      setTelefone(formatado);
    }
  };

  const formatarCPF = (valor) => {
    const numero = valor.replace(/\D/g, '');
    if (numero.length <= 11) {
      const formatado = numero.replace(
        /(\d{3})(\d{3})(\d{3})(\d{2})/,
        '$1.$2.$3-$4'
      );
      setCpf(formatado);
    }
  };

  const formatarCartao = (valor) => {
    const numero = valor.replace(/\D/g, '');
    if (numero.length <= 16) {
      const formatado = numero.replace(
        /(\d{4})(\d{4})(\d{4})(\d{4})/,
        '$1 $2 $3 $4'
      );
      setCartao(formatado);
    }
  };

  return (
    <form>
      <input
        type="text"
        value={telefone}
        onChange={(e) => formatarTelefone(e.target.value)}
        placeholder="(11) 98765-4321"
      />

      <input
        type="text"
        value={cpf}
        onChange={(e) => formatarCPF(e.target.value)}
        placeholder="123.456.789-00"
      />

      <input
        type="text"
        value={cartao}
        onChange={(e) => formatarCartao(e.target.value)}
        placeholder="1234 5678 9012 3456"
      />
    </form>
  );
}
3

Exercício: Formulário de Cadastro

Crie um formulário de cadastro completo com vários tipos de campos

Activity

Exercício: Formulário de Cadastro

Objetivo

Criar um formulário de cadastro de usuário completo com diferentes tipos de campos e validação básica.

Requisitos

  1. Campos Obrigatórios

    • Nome completo (texto)
    • Email (email)
    • Senha (password)
    • Confirmar senha (password)
    • Data de nascimento (date)
    • Gênero (radio)
    • País (select)
    • Aceitar termos (checkbox)
  2. Campos Opcionais

    • Telefone (tel)
    • Bio (textarea)
    • Interesses (múltiplos checkboxes)
    • Nível de experiência (range)
  3. Funcionalidades

    • Mostrar/ocultar senha
    • Validação de senha (mínimo 8 caracteres)
    • Verificar se senhas coincidem
    • Botão submit desabilitado até aceitar termos
    • Mostrar dados ao submeter

Template Inicial

import React, { useState } from 'react';

function FormularioCadastro() {
  const [formData, setFormData] = useState({
    nome: '',
    email: '',
    senha: '',
    confirmarSenha: '',
    dataNascimento: '',
    genero: '',
    pais: '',
    telefone: '',
    bio: '',
    interesses: [],
    experiencia: 1,
    aceitarTermos: false
  });

  const [mostrarSenha, setMostrarSenha] = useState(false);
  const [erros, setErros] = useState({});

  // Seu código aqui

  return (
    <form>
      {/* Implemente o formulário aqui */}
    </form>
  );
}

export default FormularioCadastro;

Dicas

  • Use um único handler para todos os campos quando possível
  • Trate checkboxes e campos de array separadamente
  • Valide em tempo real para melhor UX
  • Use fieldsets para agrupar campos relacionados

Solução

Clique para ver a solução
import React, { useState } from 'react';

function FormularioCadastro() {
  const [formData, setFormData] = useState({
    nome: '',
    email: '',
    senha: '',
    confirmarSenha: '',
    dataNascimento: '',
    genero: '',
    pais: '',
    telefone: '',
    bio: '',
    interesses: [],
    experiencia: 1,
    aceitarTermos: false
  });

  const [mostrarSenha, setMostrarSenha] = useState(false);
  const [erros, setErros] = useState({});
  const [enviado, setEnviado] = useState(false);

  const paises = ['Brasil', 'Portugal', 'Estados Unidos', 'Canadá', 'Argentina'];
  const interessesOpcoes = ['Tecnologia', 'Esportes', 'Música', 'Viagens', 'Leitura', 'Culinária'];

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    
    if (type === 'checkbox' && name === 'aceitarTermos') {
      setFormData(prev => ({
        ...prev,
        [name]: checked
      }));
    } else if (name === 'interesses') {
      setFormData(prev => ({
        ...prev,
        interesses: checked
          ? [...prev.interesses, value]
          : prev.interesses.filter(item => item !== value)
      }));
    } else {
      setFormData(prev => ({
        ...prev,
        [name]: value
      }));
    }

    // Limpar erro do campo quando usuário começa a digitar
    if (erros[name]) {
      setErros(prev => {
        const newErros = { ...prev };
        delete newErros[name];
        return newErros;
      });
    }
  };

  const validarFormulario = () => {
    const novosErros = {};

    if (!formData.nome.trim()) {
      novosErros.nome = 'Nome é obrigatório';
    }

    if (!formData.email.includes('@')) {
      novosErros.email = 'Email inválido';
    }

    if (formData.senha.length < 8) {
      novosErros.senha = 'Senha deve ter no mínimo 8 caracteres';
    }

    if (formData.senha !== formData.confirmarSenha) {
      novosErros.confirmarSenha = 'Senhas não coincidem';
    }

    if (!formData.dataNascimento) {
      novosErros.dataNascimento = 'Data de nascimento é obrigatória';
    }

    if (!formData.genero) {
      novosErros.genero = 'Selecione um gênero';
    }

    if (!formData.pais) {
      novosErros.pais = 'Selecione um país';
    }

    if (!formData.aceitarTermos) {
      novosErros.aceitarTermos = 'Você deve aceitar os termos';
    }

    setErros(novosErros);
    return Object.keys(novosErros).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    
    if (validarFormulario()) {
      setEnviado(true);
      console.log('Formulário enviado:', formData);
    }
  };

  if (enviado) {
    return (
      <div style={{ padding: '20px', backgroundColor: '#e8f5e9', borderRadius: '8px' }}>
        <h2>Cadastro realizado com sucesso!</h2>
        <h3>Dados enviados:</h3>
        <pre>{JSON.stringify(formData, null, 2)}</pre>
        <button onClick={() => {
          setEnviado(false);
          setFormData({
            nome: '',
            email: '',
            senha: '',
            confirmarSenha: '',
            dataNascimento: '',
            genero: '',
            pais: '',
            telefone: '',
            bio: '',
            interesses: [],
            experiencia: 1,
            aceitarTermos: false
          });
        }}>
          Novo Cadastro
        </button>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '600px', margin: '0 auto' }}>
      <h1>Formulário de Cadastro</h1>

      <fieldset>
        <legend>Informações Pessoais</legend>
        
        <div>
          <label>
            Nome Completo *
            <input
              type="text"
              name="nome"
              value={formData.nome}
              onChange={handleChange}
              style={{ width: '100%' }}
            />
          </label>
          {erros.nome && <span style={{ color: 'red' }}>{erros.nome}</span>}
        </div>

        <div>
          <label>
            Email *
            <input
              type="email"
              name="email"
              value={formData.email}
              onChange={handleChange}
              style={{ width: '100%' }}
            />
          </label>
          {erros.email && <span style={{ color: 'red' }}>{erros.email}</span>}
        </div>

        <div>
          <label>
            Senha *
            <div style={{ position: 'relative' }}>
              <input
                type={mostrarSenha ? 'text' : 'password'}
                name="senha"
                value={formData.senha}
                onChange={handleChange}
                style={{ width: '100%' }}
              />
              <button
                type="button"
                onClick={() => setMostrarSenha(!mostrarSenha)}
                style={{ position: 'absolute', right: '5px', top: '5px' }}
              >
                {mostrarSenha ? 'Ocultar' : 'Mostrar'}
              </button>
            </div>
          </label>
          {erros.senha && <span style={{ color: 'red' }}>{erros.senha}</span>}
        </div>

        <div>
          <label>
            Confirmar Senha *
            <input
              type="password"
              name="confirmarSenha"
              value={formData.confirmarSenha}
              onChange={handleChange}
              style={{ width: '100%' }}
            />
          </label>
          {erros.confirmarSenha && <span style={{ color: 'red' }}>{erros.confirmarSenha}</span>}
        </div>

        <div>
          <label>
            Data de Nascimento *
            <input
              type="date"
              name="dataNascimento"
              value={formData.dataNascimento}
              onChange={handleChange}
              max={new Date().toISOString().split('T')[0]}
            />
          </label>
          {erros.dataNascimento && <span style={{ color: 'red' }}>{erros.dataNascimento}</span>}
        </div>

        <div>
          <label>
            Telefone
            <input
              type="tel"
              name="telefone"
              value={formData.telefone}
              onChange={handleChange}
              placeholder="(11) 98765-4321"
            />
          </label>
        </div>
      </fieldset>

      <fieldset>
        <legend>Gênero *</legend>
        <label>
          <input
            type="radio"
            name="genero"
            value="masculino"
            checked={formData.genero === 'masculino'}
            onChange={handleChange}
          />
          Masculino
        </label>
        <label>
          <input
            type="radio"
            name="genero"
            value="feminino"
            checked={formData.genero === 'feminino'}
            onChange={handleChange}
          />
          Feminino
        </label>
        <label>
          <input
            type="radio"
            name="genero"
            value="outro"
            checked={formData.genero === 'outro'}
            onChange={handleChange}
          />
          Outro
        </label>
        {erros.genero && <span style={{ color: 'red' }}>{erros.genero}</span>}
      </fieldset>

      <fieldset>
        <legend>Localização</legend>
        <label>
          País *
          <select
            name="pais"
            value={formData.pais}
            onChange={handleChange}
            style={{ width: '100%' }}
          >
            <option value="">Selecione um país</option>
            {paises.map(pais => (
              <option key={pais} value={pais}>{pais}</option>
            ))}
          </select>
        </label>
        {erros.pais && <span style={{ color: 'red' }}>{erros.pais}</span>}
      </fieldset>

      <fieldset>
        <legend>Sobre Você</legend>
        <label>
          Bio
          <textarea
            name="bio"
            value={formData.bio}
            onChange={handleChange}
            rows="4"
            style={{ width: '100%' }}
            placeholder="Fale um pouco sobre você..."
          />
        </label>

        <div>
          <label>
            Nível de Experiência: {formData.experiencia}
            <input
              type="range"
              name="experiencia"
              min="1"
              max="10"
              value={formData.experiencia}
              onChange={handleChange}
              style={{ width: '100%' }}
            />
          </label>
        </div>
      </fieldset>

      <fieldset>
        <legend>Interesses</legend>
        {interessesOpcoes.map(interesse => (
          <label key={interesse}>
            <input
              type="checkbox"
              name="interesses"
              value={interesse}
              checked={formData.interesses.includes(interesse)}
              onChange={handleChange}
            />
            {interesse}
          </label>
        ))}
      </fieldset>

      <div style={{ margin: '20px 0' }}>
        <label>
          <input
            type="checkbox"
            name="aceitarTermos"
            checked={formData.aceitarTermos}
            onChange={handleChange}
          />
          Aceito os termos e condições *
        </label>
        {erros.aceitarTermos && <span style={{ color: 'red' }}>{erros.aceitarTermos}</span>}
      </div>

      <button
        type="submit"
        disabled={!formData.aceitarTermos}
        style={{
          padding: '10px 20px',
          backgroundColor: formData.aceitarTermos ? '#4caf50' : '#ccc',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: formData.aceitarTermos ? 'pointer' : 'not-allowed'
        }}
      >
        Cadastrar
      </button>
    </form>
  );
}

export default FormularioCadastro;

Desafios Extras

  1. Validação de CPF: Adicione campo e validação de CPF
  2. Upload de foto: Adicione campo para foto de perfil
  3. Endereço completo: CEP com busca automática
  4. Força da senha: Indicador visual de força
  5. Confirmação por email: Simular envio de email
3 content items