← React: componentes, estado e decisões de interface
04 / 10 · 60 MIN

Formulários, contratos DOM e acessibilidade

Definir quem controla cada campo, associar labels aos inputs e diagnosticar edição, submissão e perda de foco sem confundir estado React com identidade DOM.

Escolher a propriedade do valor

Um input não controlado pode receber defaultValue para o valor inicial; depois o browser mantém o valor editado. Um input controlado recebe value, ou checked no caso da checkbox, e a aplicação atualiza esse estado em resposta à edição. Escolhe conforme o requisito: se outra parte da interface precisa de reagir ao texto em cada alteração, estado controlado pode ser apropriado.

Não trates defaultValue como uma ligação permanente ao estado. Também não alteres inadvertidamente o modo durante a vida do mesmo input. Um valor que começa undefined e depois passa a string mistura dois contratos.

Inicializa com string vazia ou normaliza com ?? quando a intenção é controlo desde o primeiro render.

Atualização síncrona e edição legível

No campo email, onChange lê event.target.value e atualiza imediatamente o estado que alimenta value. Se adiares esse setter para um timer, a prop anterior continua a controlar o input entretanto e pode contrariar a edição. Adiar uma pesquisa remota é uma responsabilidade diferente de adiar a representação do texto introduzido.

Também merece atenção transformar o texto a cada tecla: substituir sempre por maiúsculas pode deslocar o cursor quando a edição ocorre no meio. Se uma normalização for necessária, define o momento e preserva o comportamento de seleção pretendido.

A aula não exige implementar uma máscara completa; exige reconhecer que o valor controlado e a experiência de edição têm de permanecer coerentes.

Checkboxes e intenção readonly

Uma checkbox tem um estado de marcação expresso por checked. event.target.value é o valor do campo, não esse booleano. No exemplo, enabled recebe event.target.checked e volta ao input pela prop checked. Se apenas quiseres uma marcação inicial sem controlo contínuo, usa defaultChecked.

Um campo deliberadamente não editável deve tornar essa intenção explícita, por exemplo com readOnly num input que recebe value. Acrescentar um handler vazio para suprimir um aviso não cria edição funcional e pode esconder a decisão de produto.

A distinção essencial é entre campo editável com atualização, campo inicialmente preenchido e campo cujo valor deve permanecer apenas para leitura.

Labels que identificam o controlo certo

Um label pode conter o input ou associar-se a ele por htmlFor e id correspondentes. No exemplo, o email usa a relação explícita e a checkbox usa aninhamento. A associação permite ao browser ligar a ativação do label ao controlo e fornece uma identificação programática relevante para tecnologias de apoio.

Proximidade visual e className igual não criam essa relação. Num componente reutilizado duas vezes, um id fixo repetido pode produzir uma associação ambígua. useId permite gerar o id da instância para o par label/input. Este mecanismo serve a associação do controlo; a identidade dos itens de uma lista continua a precisar dos ids estáveis dos dados, conforme a aula anterior.

Nome do campo e submissão

id identifica um elemento e pode ligar o label; name identifica o campo nos dados do formulário. Ter id=email sem name não é uma substituição desse contrato de submissão. Um botão dentro de form também precisa de intenção clara: type=submit pede submissão, enquanto type=button permite uma ação própria, como pré-visualização.

No exemplo, onSubmit chama preventDefault e atualiza uma nota local. Isto evita a navegação normal, mas não envia nem confirma dados remotamente. Se outra implementação usar FormData, deve identificar os nomes esperados e interpretar os campos efetivamente incluídos.

A presença de um handler React não transforma automaticamente todos os elementos visuais em dados enviados.

Quando o foco desaparece

Se o valor controlado é atualizado corretamente mas o foco desaparece a cada tecla, examina a identidade da subárvore. Uma key gerada com Math.random em cada render faz React recriar o input. Definir um componente dentro de outro também pode criar um novo tipo em cada chamada e provocar resets.

Alterar o nome da variável de estado ou remover labels não resolve essa causa. Separa duas hipóteses: o mesmo nó recebeu um valor incoerente, ou o nó foi removido e criado novamente. A primeira aponta para o contrato de atualização; a segunda aponta para identidade e montagem.

Essa distinção orienta a investigação sem atribuir qualquer perda de foco ao simples facto de usar onChange.

Evidência e limites do exemplo

A nota Recorded locally descreve apenas uma atualização em memória. Não deve ser interpretada como resposta de um serviço, gravação persistente ou prova de validação completa. Na revisão de um formulário, regista quem controla o valor, como a alteração regressa ao estado, como o label identifica o input e que ação o botão desencadeia.

Se a página tiver vários campos reutilizados, verifica as associações por instância. Se houver trabalho remoto, define separadamente pedidos, erros, cancelamento e respostas fora de ordem. Os casos desta aula avaliam a leitura desses contratos através de escolhas e cenários.

Não exigem executar código ou demonstram uma auditoria integral de acessibilidade da aplicação.

import {useId, useState} from 'react';

export default function ContactDraft() {
  const emailId = useId();
  const [email, setEmail] = useState('');
  const [enabled, setEnabled] = useState(false);
  const [receipt, setReceipt] = useState('Nothing recorded locally');
  function recordLocally(event) {
    event.preventDefault();
    setReceipt(`Recorded locally: ${email}; enabled=${enabled}`);
  }
  return <form onSubmit={recordLocally}>
    <label htmlFor={emailId}>Contact email</label>
    <input id={emailId} name="email" type="email" value={email}
      onChange={event => setEmail(event.target.value)} />
    <label><input name="enabled" type="checkbox" checked={enabled}
      onChange={event => setEnabled(event.target.checked)} /> Enable contact</label>
    <button type="submit">Record locally</button>
    <p>{receipt}</p>
  </form>;
}
NA PRÁTICA

email é sempre uma string e enabled um booleano. O label separado usa o mesmo id do input; a checkbox está aninhada no seu label. O handler cancela a submissão normal e regista uma nota local. Não demonstra entrega, aceitação ou validação por um servidor.

Armadilhas comuns

Usar value sem atualizar o estado; ler value numa checkbox quando se pretende checked; alternar undefined e string; repetir ids; usar keys novas por tecla; chamar a uma nota local confirmação do servidor.

Tópicos relacionados: Estrutura do estado e identidade dos componentes · Effects, refs e sincronização externa · Testar comportamentos e delimitar a evidência

Leva esta ideia contigo

O campo controlado recebe um valor coerente e devolve alterações pelo handler. Labels, ids, nomes e tipos de botão têm contratos próprios. A revisão deve preservar a edição e descrever com precisão o resultado observado.

Criar conta

Referência: Uncontrolled defaults · React 19.3.0; JSX