Controlled vs uncontrolled forms in React
The form
Let's say you have a simple form in React with an input and a button.
const submitForm = (e) => {
e.PreventDefault();
//do something with input
}
return (
<form onSubmit={submitForm}>
<label htmlFor="myInput">
<input id="myInput" />
<button>Submit</button>
</form>
)
Just to note, whereas in HTML you use label for, in React it's label htmlFor
Controlled form
When you submit the form you want to do something with what's in the input. The 'React' method of doing this is to use a state hook:
const [inputState, setInputState] = useState('');
const submitForm = (e) => {
e.PreventDefault();
console.log(inputState);
}
return (
<form>
<label htmlFor="myInput">
<input
id="myInput"
value={inputState}
onChange={(e) => setInputState(e.target.value} />
<button>Submit</button>
</form>
)
This is called a controlled form because React is controlling the form's value. For every character the user types we set the state to be the whole input and show that input in the input field.
The alternative is an uncontrolled form. There are two ways of doing this, one with useRef and one which looks even more like ordinary JavaScript.
Uncontrolled form: useRef
One of the things that useRef does is to refer to a DOM element, so you can refer to it the same way you do in JavaScript.
const inputRef = useRef();
const submitForm = (e) => {
e.PreventDefault();
console.log(inputRef.value);
}
return (
<form>
<label htmlFor="myInput">
<input id="myInput" ref={inputRef} />
<button>Submit</button>
</form>
)
To me it feels a bit like cheating at React, because I'm making it more like JavaScript and therefore going with what I know. But in reality there's nothing wrong with this method.
Uncontrolled form: JavaScript
Sometimes you don't have to re-invent the wheel.
const submitForm = (e) => {
e.PreventDefault();
console.log(e.currentTarget.myInput);
}
return (
<form>
<label htmlFor="myInput">
<input id="myInput" />
<button>Submit</button>
</form>
)
Simple, right? I like this one because it feels 'normal' ie what you do in JavaScript. And it involves less coding.
Although I did find that Jest/React Testing Library couldn't find e.currentTarget.myInput - I had to refer to e.currentTarget.children[1] instead (the first child is the label).
Which to use
If you have something this small and simple then it depends a bit on personal preference and what fits in with whatever else you have in your app.
If you have a complicated input that you want to validate as it goes or only enable the submit button once they've typed a certain number of characters, then a controlled form allows you to control that.
However, the trouble with a controlled form is that every time the onChange handler updates, the whole thing re-renders. Which wouldn't be too much of a problem in this simple example because there's not a lot to re-render. But if your form is long with lots of inputs all causing re-renders on every character typed, it's going to slow everything down.