Table of Contents
If you are starting with handling user inputs in React, you might have come across the following warning:
A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
In this tutorial, we will learn why this warning occurs and how to solve it.
Consider the following component:
1import { useState } from "react"23function App() {4 const [email, setEmail] = useState()5 return (6 <div className="App">7 <label htmlFor="email">Email:</label>8 <input9 type="text"10 name="email"11 id="email"12 value={email}13 onChange={e => setEmail(e.target.value)}14 />15 </div>16 )17}1819export default App
If you run the above code in your application, type something in the input, and open the browser console, you will see the same warning:
At the first glance, you might not be able to figure out what is the issue here,
but if you observe, you will see that we are initializing the email using useState without any values.
When a state is initialized without passing any values, it will be undefined
.
So when the user types something, the onChange
handler will be triggered, which will set the value of email to something defined.
In short, when the value of email was undefined, it was an uncontrolled input and when the user typed something,
it became a controlled input since the onChange
handler updated the value of the email.
React doesn't recommend switching an input between controlled and uncontrolled.
Controlled inputs
Let's first see how can we make the above example controlled.
We can convert the above component to controlled by simply passing an empty string as the initial value to the useState
hook.
1import { useState } from "react"23function App() {4 const [email, setEmail] = useState("")5 return (6 <div className="App">7 <label htmlFor="email">Email:</label>8 <input9 type="text"10 name="email"11 id="email"12 value={email}13 onChange={e => setEmail(e.target.value)}14 />15 </div>16 )17}1819export default App
Now if you refresh and type something on the input, you would see the warning gone.
Uncontrolled Input
As you have seen, in controlled input we make use of some state machine (local/global) to store the current value of the input. In the case of uncontrolled inputs, the value of the input field is stored in the DOM itself. We just pass a reference to the input and access the value of the input using the reference.
Let's see this with the help of an example:
1import React, { useRef } from "react"23const UncontrolledComponent = () => {4 const inputRef = useRef()5 const formSubmitHandler = e => {6 e.preventDefault()7 alert("Email: " + inputRef.current.value)8 }9 return (10 <div className="App">11 <form onSubmit={formSubmitHandler}>12 <label htmlFor="email">Email:</label>13 <input type="text" name="email" id="email" ref={inputRef} />14 <input type="submit" value="Submit" />15 </form>16 </div>17 )18}1920export default UncontrolledComponent
In the above example:
- We are declaring a reference using the
useRef
hook and passing it to the email input. - When the form is submitted, we are able to access it using
inputRef.current.value
-
We are not controlling the value entered by the user at any point of time.
Advantages of controlled inputs over uncontrolled inputs
As you have seen already,
- We do not need a form enclosing the input to have controlled inputs.
- In controlled inputs, since we can access the value of the input after each change, we can have input validation every time the user types a character. In case of uncontrolled inputs we can run validation only when the user submits the form.
Let's see the validation part in controlled components in the following example:
1import { useState } from "react"23function App() {4 const [email, setEmail] = useState("")5 const [error, setError] = useState("")67 const inputChangeHandler = e => {8 const value = e.target.value9 setEmail(e.target.value)10 if (11 !/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i.test(12 value13 )14 ) {15 setError("Invalid Email")16 } else {17 setError("")18 }19 }20 return (21 <div className="App">22 <div className="form-control">23 <label htmlFor="email">Email:</label>24 <input25 type="text"26 name="email"27 id="email"28 value={email}29 onChange={inputChangeHandler}30 />31 <p className="error">{error && error}</p>32 </div>33 </div>34 )35}3637export default App
Here every time the user types a character, we validate if it is the correct email and if it is not, we display the error message.
Before running the app, let's add some styles:
1body {2 margin: 20px auto;3 text-align: center;4}5input,6label {7 margin-right: 5px;8}910.error {11 margin: 5px 0;12 color: red;13}
Now if you run the app and type an incorrect email, you should be able to see the error is displayed.
You can learn more about form validation in react in one of my previous posts.
Source code
You can download the source code here.
Do follow me on twitter where I post developer insights more often!
Leave a Comment