Controlled and uncontrolled inputs

Understand the difference and peculiarities of each one

Renato Ribeiro
Renato Ribeiro

If you have been using React for some time, or even if you are a beginner, you probably have seen this error around:

But after all, what is this controlled input and uncontrolled input?

# Uncontrolled Input

In summary, it is even redundant to say, that, uncontrolled input is when he controls the value himself, he is self-sufficient and does not like anyone to rule him.

const Component = () => {
return <input />;
};

If you notice, what happens is that when I type something in the input, I don't save the value anywhere. The input itself saves the value that I type, natively. The state is coupled to the input element.

# Controlled Input

Since we know that the controlled input is the opposite, it is easy to guess that it is not that self-sufficient, right? Now it depends on us to save its value whenever it is changed and pass it back to him. However, the input still dictates the rules, after all, it is the input that will tell us when someone changes its value.

const Component = () => {
const [value, setValue] = React.useState('');
return <input value={value} onChange={e => setValue(e.currentTarget.value)} />;
};

Pay attention to what happens in the example above:

  1. When I type in the input, the onChange event is fired. This is the callback with which the input tells the world that it has changed, that its value has changed.
  2. I save this value in the component's hook state, and when the component's state changes, the component renders again.
  3. After re-rendering, the value of the input will be "replenished" with the value I saved in the state because the input is bound: <input value={value} />

In short, you have the impression that the input is acting in the same way as in the previous case of uncontrolled input, but in reality what happens behind it is different. The input sends the new value to the state, and the state causes the component to render the input again with its new value. It does not change itself (uncontrolled), it orders the state to change its own value.

That way, if you change the state manually, the value of the input will change too. Now it is the state that controls the value of the input, the state is decoupled from the input.

At first sight, the controlled input seems more complicated, isn't it? And it may even be. In fact, it is much more verbose (you need to write more code, as you need to save the value). But do not fool yourself: it is much more powerful, and it makes more sense for the way React works.

I'll explain. React was designed to be reactive (suggestive, isn't it?). With each simple change of state or prop, React will render it again.

So now understand the case: you have an input that has a pre-defined value:

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

I just won't be able to change its value because React understands that input value should be “Renato”, and by default, React identifies that when an input is mounted with a predefined value, this is a controlled input. So now it is only updated if the value changes by the code. But value is a string, it will never change. It's why we need the state to change the value to us.

# Back to the initial error

Now that we know the difference, what about that damn mistake? Why does it happen? Now it's a little easier to understand.

The error most often happens because of React's typechecking, and not because of the literal practice of changing an input from "uncontrolled" to "controlled".

When we are going to save the value of our input in the state, many times, by mistake or ignorance, we set the initial state to null or undefined. This means that when the component is mounted, that is, it is rendered for the first time, this value is passed to the input (null or undefined), so React understands that this is an uncontrolled input. And then, the first time we type something, it starts to set a string to the state value, which is passed on to the value of the input, whose behavior belongs to a controlled input.

To fix it, just start the state with an empty string.

Wrong:

const Component = () => {
const [value, setValue] = React.useState(null);
return <input value={value} /* ... */ />;
};

Right:

const Component = () => {
const [value, setValue] = React.useState('');
return <input value={value} /* ... */ />;
};

Follow me on twitter, and github.