Formulários
Quando você digita em um input HTML simples, o navegador cuida do valor por conta própria: armazena o que você digitou e redesenha o cursor sem nenhum código seu. Conecte o value desse input a um pedaço de estado e atualize esse estado no onChange, e o input começa a funcionar como tudo mais em React: o estado contém a verdade sobre o que está na página, e o input reflete isso. Isso é um input controlado, aquele onde React decide o que é mostrado a cada momento.
Aqui está um formulário pequeno com um input de texto:
function NameForm() {
const [text, setText] = useState('')
function handleSubmit(event) {
event.preventDefault()
console.log(text)
}
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={e => setText(e.target.value)} />
<button>Submit</button>
</form>
)
}O value do input vem do estado text, e cada digitação dispara onChange, que chama setText com o novo valor. O estado e o input permanecem sincronizados porque React re-renderiza o input com qualquer text que esteja atualmente guardado. Enviar um formulário recarrega a página por padrão, então handleSubmit chama event.preventDefault() primeiro, depois faz o que a aplicação precisa, registrando o texto aqui, enviando-o para um servidor em outro lugar.
Inputs controlados e não controlados
O input acima é um input controlado, porque seu valor vive no estado React e React decide o que ele exibe. Dê a ele um value sem um onChange, e o input fica somente leitura: React continua pintando o mesmo valor a cada render, e nada que você digita jamais chega ao estado, então combine os dois sempre que um campo for controlado. Um input também pode ser não controlado: o DOM mantém rastreamento do seu próprio valor, e você o lê apenas quando precisa, através de uma ref para um único campo (uma forma de acessar um elemento DOM diretamente, coberta completamente em Refs e o DOM) ou através do FormData do formulário para um formulário inteiro na hora do envio. Inputs controlados dão ao componente o valor atual a cada digitação, o que é o que validação ao vivo, contadores de caracteres, ou campos dos quais outras partes da UI dependem precisam. Inputs não controlados pulam uma renderização por digitação e procuram pelo valor apenas na hora do envio, o que é menos código para um campo simples que mais nada precisa observar.
Checkboxes
Checkboxes seguem o mesmo padrão, mas seu estado vive em checked em vez de value:
function Newsletter() {
const [subscribed, setSubscribed] = useState(false)
return (
<label>
<input
type="checkbox"
checked={subscribed}
onChange={e => setSubscribed(e.target.checked)}
/>
Subscribe to updates
</label>
)
}e.target.checked é um booleano, e subscribed determina se a caixa está marcada: uma prop, um change handler, mesma forma que o input de texto.
Select
Um <select> também recebe um value, definido no próprio select em vez de na option selecionada:
function ColorPicker() {
const [color, setColor] = useState('red')
return (
<select value={color} onChange={e => setColor(e.target.value)}>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</select>
)
}Defina value no select, e React escolhe a option correspondente para você. Não há necessidade de adicionar um atributo selected a nenhum dos elementos option.
Textarea
Em HTML, uma textarea carrega seu texto entre as tags de abertura e fechamento. React a trata como um input como qualquer outro, então o texto chega através de uma prop value e onChange a mantém sincronizada:
function Feedback() {
const [message, setMessage] = useState('')
return (
<textarea
value={message}
onChange={e => setMessage(e.target.value)}
rows={4}
/>
)
}rows define a altura inicial da caixa. defaultValue é o equivalente não controlado de value aqui: define o texto inicial uma vez e deixa o DOM rastreá-lo daí em diante.
Botões de radio
Vários inputs compartilham um name, que diz ao navegador que pertencem juntos. Apenas um pode ser escolhido. Em React o grupo compartilha um pedaço de estado, e cada checked do input compara contra ele:
function ShippingSpeed() {
const [speed, setSpeed] = useState('standard')
return (
<fieldset>
<legend>Shipping speed</legend>
{['standard', 'express', 'overnight'].map(option => (
<label key={option}>
<input
type="radio"
name="speed"
value={option}
checked={speed === option}
onChange={e => setSpeed(e.target.value)}
/>
{option}
</label>
))}
</fieldset>
)
}fieldset e legend rotulam o grupo para leitores de tela, e cada label recebe uma key porque as opções vêm de um array (listas e chaves).
checked={speed === option} é verdadeiro para exatamente uma opção, então um valor de estado cobre o grupo inteiro, enquanto um checkbox carrega seu próprio booleano.
Enviando com uma action
Um <form> também pode receber uma função action. React a chama quando o formulário é enviado e passa um objeto FormData contendo os valores do formulário:
function Signup() {
function handleSignup(formData) {
const values = Object.fromEntries(formData)
console.log(values.email, values.password)
}
return (
<form action={handleSignup}>
<input name="email" type="email" />
<input name="password" type="password" />
<button>Sign up</button>
</form>
)
}Não há evento aqui, então não há nada em que chamar preventDefault(). React para o carregamento padrão da página do navegador para você.
Object.fromEntries(formData) transforma aquele FormData em um objeto simples em um passo. As chaves vêm do atributo name de cada input, é por isso que cada input no formulário precisa de um. Quando vários inputs compartilham um nome e podem ser enviados juntos, como um grupo de checkboxes, apenas o último valor sobrevive, e formData.getAll os lê todos.
Os inputs acima são não controlados: o DOM mantém cada valor até o envio. Isso importa para o comportamento que surpreende pessoas vindas de onSubmit: React reseta o formulário uma vez que a action é resolvida, limpando campos não controlados como estes. Um campo controlado re-renderiza do estado e mantém seu valor.
Cada caminho serve a um tipo diferente de formulário. Uma action cabe em um formulário cujo trabalho é coletar valores e passá-los adiante, geralmente para um servidor, onde a espera é real e o resultado tem que voltar para a UI. Os valores chegam coletados e nomeados. onSubmit com inputs controlados cabe em um formulário que tem que reagir enquanto o usuário digita, para validação ao vivo, uma contagem de caracteres, ou um campo que muda o que o resto do formulário mostra.
Nota de versão
Form actions chegaram no React 19. No React 18 e anteriores, <form action={handleSignup}> não lança erro. React descarta o atributo, porque uma função não é um valor de atributo válido, e avisa sobre isso em desenvolvimento. O formulário então é enviado para a URL atual, então a página recarrega e a função nunca é executada. O envio nessas versões passa por onSubmit com event.preventDefault().
value do input vem do estado, e onChange atualiza esse estado. Uma vez que isso faz sentido para um input de texto, funciona da mesma forma para selects e textareas, e para checkboxes e botões de radio com checked carregando o estado. Enviar é seu próprio passo: um envio de formulário executa uma função que você escreve, e aquela função decide o que acontece com os valores.
Próximo: Levantando estado, onde dois componentes precisam compartilhar o mesmo pedaço de estado.

