If you'll go to the official React website it says that the recommended way to use inputs is to control them via React state. It also mentions that in some cases you can go with an uncontrolled option but do not say what are these cases explicitly. Let’s try to dive into it and see the pros and cons of this approach.
Controlled input values are being kept in the local state. Every time the user changes the content of the input, the
onChange
function updates the React state, Component re-renders with the new value passed to Input.const SimpleControlledForm = () => {
const [email, setEmail] = useState('')
const [name, setName] = useState('')
const onSubmit = useCallback((e) => {
e.preventDefault()
sendData({ email, name })
}, [email, name])
return (
<form onSubmit={onSubmit}>
<input name="email" value={email} onChange={(e) => setEmail(e.target.value)} />
<input name="name" value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit">Submit</button>
</form>
)
}
On the other hand, uncontrolled inputs are being handled by the browser itself. So you don’t have direct control over inputs value in the component, but you can use ref to get access to it.
const SimpleUncontrolledForm = () => {
const onSubmit = useCallback(e => {
e.preventDefault()
const { email, name } = e.target
sendData({ email: email.value, name: name.value })
e.target.reset()
}, [])
return (
<form onSubmit={onSubmit}>
<input name="email" />
<input name="name" />
<button type="submit">Submit</button>
</form>
)
}
Looking at these two examples you can clearly say that the second one looks cleaner and less verbose than the first one. You don’t need to instantiate state, pass the value to each input and override onChange handlers.
An uncontrolled form doesn’t have state, so every time you type, your component doesn’t re-render, increasing overall performance.
By leveraging uncontrolled inputs you’re trusting browser with form management and become closer to the native implementation. Why do you need to write something that was already created anyway? 🤔
Use refs. With new Hooks API, refs became a more stable and better way to control DOM. Consider the following example where you need to access forms values outside of the
onSubmit
function.const SimpleUncontrolledForm = () => {
const form = useRef()
const onSubmit = useCallback(
e => {
e.preventDefault()
const { email, name } = e.target
sendData({ email: email.value, name: name.value })
e.target.reset()
}, []
)
const sendValues = () => {
const { email, name } = form.current
sendData({ email: email.value, name: name.value })
}
return (
<>
<form ref={form} onSubmit={onSubmit}>
<input name="email" />
<input name="name" />
<button type="submit">Submit</button>
</form>
<button onClick={sendValues}>Send values</button>
</>
)
}
Browser API provides you with a lot of useful features to handle forms and inputs.
Sure! React-Hook-Form allows you to build a form and perform validation on it without the hassle. Unform helps you build complex forms with nested fields, validation using uncontrolled components.
Basically you need to choose controlled inputs when you need to re-render your component on every type. For example, you want to display or send all form values as you type (imagine autocomplete field for the search bar). Or if you need to pass input value to child component through props. There are quite a few cases where you have to use it.
I believe that uncontrolled components are currently undervalued and should have more presence in modern React applications. Indeed they’re not gonna solve all your problems but would help increase the performance and reliability of your components in many cases.