Entendendo inputs controlados e não controlados no React.

Renato Ribeiro
5 min readAug 8, 2017

--

Se você já usa React há algum tempo, ou até mesmo se for iniciante, provavelmente já deve ter visto este erro por aí:

Erro que acontece quando o input é mudado de ‘não controlado’ para ‘controlado’ ou vice versa.

Mas afinal, o que é esse tal de input controlado (Controlled Input) e input não controlado (Uncontrolled Input)?

Uncontrolled Input

Em síntese, é até redundante dizer, mas, o input não controlado é quando ele controla o valor de si próprio, ele é auto-suficiente e não gosta que ninguém mande nele.

class App extends React.Component {
handleInputRef = (input) => {
this.input = input;
};
handleSend = () => {
alert(`Seu nome é ${this.input.value}`);
};

render() {
return (
<div>
Digite seu nome: <input ref={this.handleInputRef} />
<button onClick={this.handleSend}>Enviar</button>
</div>
);
}
}

Se você perceber nos detalhes, o que acontece é que quando eu digito algo no input, eu não salvo o valor em nenhum lugar. Ele mesmo salva o valor que eu digito (de forma nativa). E quando eu clico no botão enviar, eu simplesmente pego este input por referência e uso seu valor.

Controlled Input

Já que sabemos que o input controlado é o oposto, fica fácil adivinhar que ele não é tão auto-suficiente assim, né? Agora ele depende que salvemos o seu valor sempre que mudado, e repasse de volta para ele. Porém, é ele quem dita as regras ainda, afinal, é ele quem vai nos dizer quando alguém alterar seu valor.

class App extends React.Component {
state = {
name: '',
};
handleInputChange = (event) => {
this.setState({ name: event.target.name });
};
handleSend = () => {
alert(`Seu nome é ${this.state.name}`);
};

render() {
return (
<div>
Digite seu nome: <input onChange={handleInputChange} value={this.state.name} />
<button onClick={this.handleSend}>Enviar</button>
</div>
);
}
}

Preste atenção no que acontece no exemplo acima:

  1. Quando eu digito no input, o evento onChange é disparado. Este é o callback com que o input diz ao mundo que está mudado, que foi alterado seu valor.
  2. Eu guardo esse valor no estado do componente, e, como vocês devem saber, quando o estado do componente muda, o componente renderiza novamente (o que chamamos de rerender).
  3. Com o rerender, o valor do input será “repreenchido” com o valor que eu salvei no estado pois o input está bindado:
    <input
    value={this.state.name} />
O triângulo do input controlado

Em suma, você tem a impressão que o input está agindo da mesma maneira que no caso anterior do input não controlado, mas na realidade o que acontece por trás é diferente. O input manda o novo valor pro estado, e o estado faz o componente renderizar o input novamente com seu valor novo. Ele não muda a si mesmo, ele manda o estado mudar seu próprio valor.

A primeira vista, parece que o input controlado é mais complicado, não é mesmo? E pode até ser. De fato é muito mais verboso (você precisa escrever código a mais, pois precisa salvar o valor). Mas não se engane: é muito mais poderoso, e faz mais sentido pela forma com que o React funciona.

Eu te explico. O React foi desenhado para ser reativo (sugestivo, não?). Isto é, a cada alteração simples de state ou de prop, o React irá renderizá-lo novamente.

Então agora entenda o caso: você tem um input que tem um valor pré definido, isto é, um valor que já vem preenchido no primeiro render:

const Component = () => (
return <input type="text" value="Renato" />;
);

Eu simplesmente não vou conseguir alterar seu valor (veja com seus próprios olhos aqui). Pois o React entende que aquele valor deveria ser “Renato”, e por padrão, o React identifica que quando um input é montado com um value pré definido, este é um input controlado. Então agora ele só se atualiza por mando de outrem, no caso, nosso estado (que também poderia ser um estado global, via redux/mobx/whatever).

Nosso input agora é um “Dummy”. Ele faz o que os ‘outros’ mandam.

Se você está atento nos mínimos detalhes, deve ter se perguntado: mas quando eu digito em um input, o componente não dá rerender. Bem observado! Acontece que o React faz isso de propósito, por dois motivos:

1. Pra não dar merda. Se ele deixasse você editar o input controlado nativamente, e alguém disparasse um rerender, seu input voltaria pro valor ‘original’ no qual veio setado o value . Isto é, você não salvou em nenhum lugar, e ao renderizar novamente, você perderia o que tinha digitado.

2. Pra não dar merda. Imagina que o input atualiza quando você digita e quando o estado muda? Na prática, ele se alteraria 2x.

Voltando ao erro do início

Agora que sabemos a diferença, e aquele maldito erro? Por que acontece?
Bem, agora fica um pouco mais fácil de entender.

O erro na maioria das vezes acontece por causa do typechecking do React, e não pela literal prática de mudar um input de “não controlado” para “controlado”.

Quando vamos guardar o valor do nosso input no estado, muitas vezes, por engano ou desconhecimento, setamos o estado inicial como null ou undefined . Isto faz com que quando componente é montado, ou seja, é renderizado pela primeira vez, seja passado esse valor para o input (null ou undefined), então o React entende que este é um input não controlado. E aí, na primeira vez que digitarmos algo, ele passa a setar uma string ao valor do estado, que é repassado ao value do input, cujo comportamento pertence à um input controlado.

Para corrigir, basta iniciar o estado com uma string vazia.

class App extends React.Component {
state = {
name: '',
};
render() {
/* ... */
}
);

E não esquece de me seguir no twitter e no github

--

--