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 state e atualize esse state no onChange, e o input começa a funcionar como tudo mais em React: o state é a fonte da verdade sobre o que aparece 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 pequeno formulário 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 state text, e cada digitação dispara onChange, que chama setText com o novo valor. O state e o input ficam em sincronia porque React re-renderiza o input com o que text contém no momento. Enviar um formulário recarrega a página por padrão, então handleSubmit chama event.preventDefault() primeiro, depois faz o que o aplicativo precisa, aqui registrando o texto em console, enviando para um servidor em outro lugar.
Inputs controlados e não controlados
O input acima é um input controlado, porque seu valor vive no state do React e React decide o que ele exibe. Dê a ele um value sem um onChange, e o input fica somente para leitura: React continua pintando o mesmo valor em cada renderização, e nada que você digita nunca chega ao state, então emparelhe os dois sempre que um campo for controlado. Um input também pode ser não controlado: o DOM acompanha seu próprio valor, e você o lê apenas quando precisa, através de uma ref para um campo único (uma forma de alcançar um elemento DOM diretamente, coberta completamente em Refs e o DOM) ou através do FormData do formulário para um formulário inteiro no momento do envio. Inputs controlados dão ao componente o valor atual a cada digitação, que é o que validação em tempo real, contagem de caracteres ou campos dos quais outras partes da UI dependem necessitam. Inputs não controlados pulam uma renderização por digitação e alcançam o valor apenas no momento do envio, que é menos código para um campo simples que nada mais precisa acompanhar.
Checkboxes
Checkboxes seguem o mesmo padrão, mas seu state 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, a mesma estrutura do input de texto.
Select
Um <select> também recebe um value, definido no próprio select em vez de no option selecionado:
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 o 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 mantém tudo em sincronia:
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 a partir daí.
Radio buttons
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 state, e o checked de cada input se 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 keys).
checked={speed === option} é verdadeiro para exatamente uma opção, então um valor de state cobre todo o grupo, enquanto um checkbox carrega seu próprio booleano.
Enviar 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 para chamar preventDefault(). React para o recarregamento padrão da página do navegador para você.
Object.fromEntries(formData) transforma FormData em um objeto simples em um passo. As chaves vêm do atributo name de cada input, é por isso que todo input no formulário precisa de um. Onde vários inputs compartilham um nome e podem ser enviados juntos, como um grupo de checkboxes, apenas o último valor sobrevive, e formData.getAll lê todos eles.
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 vindo 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 state e mantém seu valor.
Cada caminho é adequado para um tipo diferente de formulário. Uma action se adequa a um formulário cujo trabalho é reunir valores e entregá-los, geralmente a um servidor, onde a espera é real e o resultado tem que voltar para a UI. Os valores chegam reunidos e nomeados. onSubmit com inputs controlados se adequa a um formulário que tem que reagir enquanto o usuário digita, para validação em tempo real, contagem de caracteres ou um campo que muda o que o resto do formulário mostra.
Version note
Form actions chegaram em React 19. Em React 18 e anteriores, <form action={handleSignup}> não dispara erro. React remove o atributo, porque uma função não é um valor de atributo válido, e avisa sobre isso no desenvolvimento. O formulário então envia para a URL atual, então a página recarrega e a função nunca é executada. Envio nessas versões passa por onSubmit com event.preventDefault().
value do input vem do state, e onChange atualiza esse state. Uma vez que isso clica para um input de texto, funciona do mesmo jeito para selects e textareas, e para checkboxes e radio buttons com checked carregando o state. Envio é seu próprio passo: um submit de formulário executa uma função que você escreve, e essa função decide o que acontece com os valores. Próximo: Levantando state, onde dois componentes precisam compartilhar o mesmo pedaço de state.

