What are uncontrolled components?
In the previous chapter, we learnt a great deal of information regarding controlled components in React and what exactly is meant by the term 'controlled' in there.
To review it quickly, a controlled component in React is a form input component whose data is controlled by React instead of the DOM itself.
As we enter anything into a controlled component, its value won't change unless our React code goes on and explicitly changes the value of the component (which is often done using the value
prop).
Uncontrolled components are the opposite of controlled components.
As we enter anything into an uncontrolled input, React can NOT step in and prevent the input elements from updating themselves unless explicitly provided a value.
The input elements are instead free to operate on their own will, just as they normally are. Thusly named — 'uncontrolled' components.
If we're coming from a vanilla JavaScript background, where we're used to directly dealing with input elements from the DOM, uncontrolled components would be the closest to this experience of ours.
In uncontrolled components, React doesn't have any control over the data entered into the components, and we directly get to programmatically query those components.
Now since React doesn't have control over the inputs when they are uncontrolled, and since we directly deal with them, we need to rely on refs in React in order to gain access to the actual DOM nodes of these uncontrolled input elements.
This is a customary practice when using uncontrolled components, which we shall see later on in this chapter as well.
When do we need uncontrolled components?
A common question popping up in the mind of any beginner React developer is that "Should I use controlled components or uncontrolled components?"
Or even that "When do we really need uncontrolled components?"
In this section, we shall answer both these questions, starting with the former.
So should we use controlled components or uncontrolled components when creating form input elements in React?
Well, this isn't any difficult to answer — clearly controlled components.
Controlled components allow us to involve React's core state utility into the equation and have access to input data directly inside of component functions. React itself encourages us to use controlled components to create form inputs.
Now over to the second question: When do we really need uncontrolled components, given that controlled components are the outright recommendation?
Recall that React is made in a way such that it can easily be plugged in existing apps. There is no need to rewrite entire apps in React before it could be leveraged within them.
In this regard, when transferring existing functionality, specifically in the case of form handling, to React, it might suit developers to temporarily rely on uncontrolled components so that they could reuse the functionality in their current form handling systems (written in JavaScript, and interacting with the DOM directly).
In other words, uncontrolled components make for an easy entry path into React when transitioning JavaScript codebases into it.
Coding controlled components is undoubtedly slightly complex; and as such, uncontrolled components offer less complexity and less entry barrier for bringing in existing functionality into React.
Long story short,
However, if it's feasible for a developer, it's an outright recommendation to rely on controlled components and convert existing form handling systems to them, even if that means coding for a couple of extra lines.
How to make uncontrolled components?
Let's now see how exactly to make uncontrolled components in React. We've already seen certain parts to this in the previous chapter but let's see them now more clearly.
In order to make a form input component in React uncontrolled:
- It should NOT have a
value
prop on it, or - The value prop should be
null
orundefined
null
or undefined
is as good as removing the attribute from the component. Thus, the latter case above is simply an extension of the first case (of there being no value prop on a form input component).When the value
prop is not specified to a form input component, or when it's null
/undefined
, React will know not to interfere with its normal behavior.
Let's see a quick example.
In the following code, we have a text input element without any value
prop on it, but with an onChange
handler to log in the console every moment the input's data changes:
function App() {
return (
<>
<p>Type anything below and then see the console:</p>
<input type="text" onChange={e => console.log(e.target.value)} />
</>
);
}
Since the input element is void of a value
prop, it isn't controlled and, thus, as we type into it, the entered data is shown right away.
In the link below, try interacting with the input field and side-by-side witnessing the logs made in the console.
Now, let's say, we wish to take this simple program a bit further and add a button to validate the entered data to be not any longer than 3 characters. The moment the button is clicked, we should make an alert in the console: 'Valid' if this condition is met, or else 'Invalid'.
Simple enough of a task.
First, let's add the button in the JSX and remove the onChange
handler from <input>
, since we don't need it:
import { useRef } from 'react';
function App() {
const inputRef = useRef();
return (
<>
<input type="text" ref={inputRef} />
<button>Check</button>
</>
);
}
Now, let's define the onClick
handler on <button>
:
import { useRef } from 'react';
function App() {
const inputRef = useRef();
function validateInput() {
if (inputRef.current.value.length <= 3) {
alert('Valid');
}
else {
alert('Invalid');
}
}
return (
<>
<input type="text" ref={inputRef} />
<button onClick={validateInput}>Check</button>
</>
);
}
Upon the button's click, we ought to retrieve the value of the <input>
, validate it against the given length requirement, and then finally make the desired alert.
Notice how we're using a ref to obtain the value of the <input>
element. As we learnt above, this is a customary routine when working with uncontrolled components in React — after all, there has to be some point where we interact with a DOM node representing a form input.
And this is pretty much how we'll interact with other input elements if they were present in our form — using refs to obtain their DOM nodes and then querying the DOM nodes directly.
To recap what we learnt in this section, in order to make a component uncontrolled, we need to have the value
prop absent from it (or if present, have it set to null
or undefined
).
But what if we really want a given input element to start off with a particular value and keep it uncontrolled?
As we just learnt, adding the value
prop ain't a feasible solution in this case because that'll make the component controlled, and we don't want that.
Now what? Well, this is something React realized long ago and provided features to work around it. These features are the defaultValue
and defaultChecked
props.
The defaultValue
prop
The defaultValue
prop is basically just the uncontrolled version of value
. Period.
There's nothing really technical to learn about it.
If we wish to apply a value
attribute to an input element in React but also wish to not transition it to a controlled component, then obviously we can't use the value
prop; instead we need to use defaultValue
.
Consider the following code where we have a text input element with an initial value to begin with. This value is set using the value
prop:
function App() {
return (
<>
<p>The following input has an initial value to begin with:</p>
<input type="text" value="Initial value" />
</>
);
}
Expectedly, since the value
prop is being used and there is nothing to update this value
prop, our component would be read-only.
But now, let's replace this value
prop with defaultValue
and see the effect produced:
function App() {
return (
<>
<p>The following input has an initial value to begin with:</p>
<input type="text" defaultValue="Initial value" />
</>
);
}
Open the linked page below and interact with the input therein.
See how we're now able to enter anything into the input without it being passed over to React before being ultimately written inside the input element.
That is, using defaultValue
, our input component has an initial value to begin with and it's also uncontrolled at the same time.
The defaultChecked
prop
Just like the defaultValue
prop is the uncontrolled complement of the value
prop, the defaultChecked
prop is the same for the checked
prop.
As we already know, the value
prop can't be applied to certain input elements, such as checkboxes and radios. In these cases, we use the checked
prop to initially check them.
But this has the consequence of transitioning the inputs into controlled components. And that's an issue if we wish to keep the inputs uncontrolled.
The defaultChecked
prop comes to the rescue here. It initially checks a checkbox or radio but doesn't make it controlled.
Below shown is an example of defaultChecked
, contrasting it with the checked
prop:
Following we have a checkbox with checked
set:
function App() {
return (
<>
<p>The following checkbox is initially checked:</p>
<label>
<input type="checkbox" checked /> Checkbox label
</label>
</>
);
}
As we click on the checkbox to uncheck it, there's no effect produced. And you know the reason by now — checked
is set and that makes the input controlled.
Following we have the same checkbox albeit with defaultChecked
instead of checked
:
function App() {
return (
<>
<p>The following checkbox is initially checked:</p>
<label>
<input type="checkbox" defaultChecked /> Checkbox label
</label>
</>
);
}
As we click on the checkbox to uncheck it now, we get the expected behavior, all thanks to the fact that the component is uncontrolled by virtue of there being no checked
prop.
This was simple, what do you say?