Uncontrolled Components are an anti-pattern

Andy Edwards
4 min readMar 30, 2016

--

This is a response to the following article: Index as a key is an anti-pattern.

Its example, to anyone who doesn’t read the code, apparently shows how if you use indexes as keys for React components, you might display data in the wrong place.

While it’s true that index keys are somewhat risker than unique keys, the author of that example, and everyone he’s convinced, have totally overlooked its real underlying problem: uncontrolled components.

If you played with the example it should have seemed immediately suspicious to you: if unique id and text it displays for a given todo are passed in from the same JS object, how can they appear in different rows?

They can’t. Read the code: it doesn’t store or pass in any text for the todos at all!

That’s right; it doesn’t tell the inputs what value to display, so they behave as uncontrolled components, storing whatever text the user typed in internally. Thus when a new todo gets inserted at the top, and its props get passed to the first input without remounting, since it has index 0, it doesn’t change the text because it doesn’t have any value property. Had the author passed in a value property (i.e. used the input as a controlled component), it would change the text of the existing input and there would be no bug, even though there’s a switcheroo and the existing input gets associated with a different todo.

Remember: the idea that the data you see should be exactly the data you stored/passed in is the cornerstone of React(/Flux/Redux) programming. It ensures predictability. Any deviations from this principle reduce predictability. Controlled components show exactly the data that you passed in. Uncontrolled components don’t necessarily show exactly the data that you passed in. Uncontrolled components are less predictable than controlled components.

I never use uncontrolled components. They’re a lot like 2-way binding, which has long seemed detrimental to me, and seemingly to the creators of React as well. I’m guessing people mainly like uncontrolled components because they take less effort to use (you don’t have to store a value, pass it in, and update it from an onChange handler). But I don’t think people who use uncontrolled components or 2-way binding really realize how much predictability and consistency they’re sacrificing.

Not convinced? Perhaps you think uncontrolled components are fine and the index keys were really the root of the problem with the aforementioned example. Well guess what: this same inconsistencies can happen with uncontrolled components even when you’re not using keys at all!

Here’s an example: a simple form that shows the first name, last name, and middle initial inputs in a different order, depending on the locale (e.g. in Hungarian the last name always comes first). [Full runnable JSFiddle]

render() {
let {language} = this.props;
if (language === 'Hungarian') {
return <form>
<input type="text" placeholder="Vezetéknév" name="lastName"/>
<input type="text" placeholder="Keresztnév" name="firstName"/>
<input type="text" placeholder="KB" style={{width: 30}} name="middleInitial"/>
</form>;
}
// other langs...
return <form>
<input type="text" placeholder="First Name" name="firstName"/>
<input type="text" placeholder="MI" style={{width: 30}} name="middleInitial"/>
<input type="text" placeholder="Last Name" name="lastName"/>
</form>;
}

As you can see, in either case the component renders a form with three inputs. This means that when the language changes, React won’t remount any of the inputs; instead it will just pass the new props to the existing inputs. Which means: the existing text in each input will remain, even though the field names got shuffled around. For example, if you type in your last name in the 3rd input in English mode, then change the language to Hungarian, and then submit the form, your last name will remain in the 3rd input and get submitted as the middle initial. Had you been passing in the values (i.e. using controlled components), your last name would have moved to the 1st input and still get submitted as the last name.

Now, the bigger picture here is that uncontrolled components are just one manifestation of internal state. And internal state has long been discouraged in the React community. So you might be thinking, is internal state an anti-pattern? I would say, in most cases, yes. However, in my experience, internal state is very often used for transitions (for example, ReactCSSTransitionGroup). It’s by no means a necessity, as these components could all pass their state changes to callbacks like controlled components, but for transitions, the convenience of internal state often outweighs the risks. Also, transitions have a special characteristic that reduces the unpredictability of internal state: internal transition state, lags behind, but always tends toward, the app state that was passed in. So theoretically there’s a finite amount of time that the transition state could be inconsistent. Uncontrolled inputs turn everything upside down: you are responsible for keeping your app state up-to-date with them, and you can lag behind indefinitely!

So before you fear indexes as keys, remember, if the index-keyed components and everything inside them are stateless, there’s no problem.

--

--