Skip to content
react

A component is changing an uncontrolled input to be controlled

Dec 12, 2022Abhishek EH5 Min Read
A component is changing an uncontrolled input to be controlled

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:

App.js
1import { useState } from "react"
2
3function App() {
4 const [email, setEmail] = useState()
5 return (
6 <div className="App">
7 <label htmlFor="email">Email:</label>
8 <input
9 type="text"
10 name="email"
11 id="email"
12 value={email}
13 onChange={e => setEmail(e.target.value)}
14 />
15 </div>
16 )
17}
18
19export 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:

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.

App.js
1import { useState } from "react"
2
3function App() {
4 const [email, setEmail] = useState("")
5 return (
6 <div className="App">
7 <label htmlFor="email">Email:</label>
8 <input
9 type="text"
10 name="email"
11 id="email"
12 value={email}
13 onChange={e => setEmail(e.target.value)}
14 />
15 </div>
16 )
17}
18
19export 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:

UncontrolledComponent.js
1import React, { useRef } from "react"
2
3const 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}
19
20export 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:

App.js
1import { useState } from "react"
2
3function App() {
4 const [email, setEmail] = useState("")
5 const [error, setError] = useState("")
6
7 const inputChangeHandler = e => {
8 const value = e.target.value
9 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 value
13 )
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 <input
25 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}
36
37export 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:

index.css
1body {
2 margin: 20px auto;
3 text-align: center;
4}
5input,
6label {
7 margin-right: 5px;
8}
9
10.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.

Invalid email

You can learn more about form validation in react in one of my previous posts.

Source code

You can download the source code here.

If you have liked article, do follow me on twitter to get more real time updates!

Leave a Comment

© 2023 CodingDeft.Com