Don’t Mix Controlled and Uncontrolled Values In React
One of the most fundamental rules in component architecture

In this article, I will be discussing one of the most fundamental rules in component architecture. This fundamental rule is often violated, leading to issues that are very hard (if not impossible) to solve.
Although I’ll be providing examples and talking in React terms, it’s all applicable to other UI libraries/frameworks, and probably to any component-based program, where components can have bi-directional communications.
Let’s begin by defining the terms controlled and uncontrolled. In one of React’s official blog posts, they define it as follows:
Data passed in as props can be thought of as controlled (because the parent component controls that data). Data that exists only in internal state can be thought of as uncontrolled (because the parent can’t directly change it).
It’s important to note here that it’s incorrect to call a component controlled or uncontrolled because a component is neither. Instead, we refer to data as controlled/uncontrolled. And indeed as we will see soon, a single component may be both controlled and uncontrolled without violating any rules, so long as it’s not done for the same value.
This rule is often violated by mixing the two concepts, creating a component with data values that are both controlled and uncontrolled. Using such a component can lead to a violation of the single source of truth principle. Additionally, as I will demonstrate later on, such a component will be incapable of handling certain scenarios.
An Example
The most common example is an input component. Such a component can take user input, but can also receive input from the application itself (for example, when setting the initial value). This bi-directional data communication is what makes this type of component prone to controlled/uncontrolled mixing issues.
Here’s a basic example of such a component:
As you can see, the component receives its initial value as a prop, as well as an onChange
handler. At this point, the value
prop is both controlled and uncontrolled since the value is communicated back and forth to the containing layer of the component via the value
and onChange
props, but, at the same time, the value is maintained in an internal state.

The first and most apparent problem with this is the violation of the single source of truth principle. That’s because you can now potentially have the same value in two different memory locations. If these locations get out of sync, your application will start doing weird things.
The less apparent problem here is the issues that you will face when trying to utilize the bi-directional communication that this component is intended to support.
Let’s say that you want to have a reset button somewhere in your application, that, upon clicking, will reset the value of your input component. To make it work we will have to tweak our component a bit to listen to changes on the value
prop. We can do that using the useEffect
hook:
That’s already making things more complex and less readable, and this is the clean version. This gets much worse when we do this in a class component, with all of that good getDerivedStateFromProps
stuff. But at least it’s working, right?
Suppose clicking the reset button passes an empty string ‘’
as a value to our component. Now, suppose that we would like the initial value to also be an empty string ‘’
. What would happen if the user types something and then clicks the reset button? Nothing!
Nothing will happen because the value
prop has not changed, it’s still an empty string, so the component will not be re-rendered. Even if we could find a way to somehow force it to re-render, the useEffect
will never be called since value
is the same.
We can, of course, add another prop called initialValue
, but that will just cover that specific case. It’s still possible to have a different scenario where we need to set the internal value to what value
already is. We can’t add another prop for every case, can we?
Lifting the State Up
The solution to this is very simple —maintain the state at the containing layer (that is, any ancestor, not just the direct parent), instead of the internal state. This lets you maintain a clean bi-directional data flow without violating any principle. It also makes your components much less complex and convoluted.
A better version of our horrible input component would look something like this:
Doesn’t that look much better? This component is not just good looks though, it also has a great personality — it doesn’t violate any principle. To complete our example, a basic app with a reset button would look something like this:
The only downside to this is that now we must have a state whenever we are using this component. But that’s a small price to pay, and most apps probably have some central state anyway.
Legally Mixing Controlled and Uncontrolled
As I mentioned earlier, the concept of controlled/uncontrolled refers to the props of a component rather than to the component itself. This means it’s perfectly OK to have an internal state in a component with some controlled props, as long as this state does not affect, nor is affected by, these props.
With that in mind, let’s add to the initial definition quoted at the beginning of the article:
A single value within a component’s data set (props and state), can be either controlled or uncontrolled, but not both. A single component can, however, have a mixture of controlled and uncontrolled values.
To illustrate this, let’s add an internal state to our TextInput
, which adds a .focused
class when the component is focused:
This component is now legally mixing controlled/uncontrolled concepts. It is controlled for the value
prop and uncontrolled for the focused
state. This is perfectly fine since we still have a single source for our truths and we won’t face the issues discussed earlier.
Switching Between Controlled and Uncontrolled On The Fly
Sometimes we need our component to be controlled in certain cases, but uncontrolled in others, for the same prop. This brings us to another key point regarding this whole concept:
A single value within a component’s data set (props and state) can be both controlled and uncontrolled, as long as it’s not at the same time.
To illustrate this, let’s expand on our previous focus example. Let’s say we want to be able to control the focus state of our input externally, but only in certain cases. When we don’t need to control it externally, we would like it to have an internal state. This allows us to avoid adding the useState
boilerplate at the container level when we don’t need it to be controlled.
This can be done by introducing 3 new props: focused
, onFocus
and onBlur
. For simplicity, I have removed the value
/onChange
props:
In the above example, leaving the focused
prop empty will turn it into an uncontrolled value, and the component will use an internal state for it. However, passing a boolean value to that prop will make it controlled, and the internal state will be ignored.
This makes it possible for us to switch between controlled/uncontrolled on the fly by passing a special value to focused
(in this case, that special value is undefined
).
The key concept here is that there cannot be a case where both the internal state and the prop are used simultaneously. If we use the prop, the internal state will be ignored and vice versa. So that value cannot be both controlled and uncontrolled at the same time, allowing us to avoid all the issues associated with that.
Conclusion
I hope this clarifies these concepts and underlines the importance of not mixing them. It is my experience that improperly mixing controlled/uncontrolled concepts for a single value can lead to very unpleasant defects and that keeping them properly separated makes the components cleaner, more readable, and more robust.