Controlled vs Uncontrolled Components in React

Tags: react, javascript
Published: 2021-08-31 04:54:36

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.

{% youtube XI21YGIfcdc %}

Consider the following component:

```jsx
import { useState } from "react"

function App() {
  const [email, setEmail] = useState()
  return (
    <div className="App">
      <label htmlFor="email">Email:</label>
      <input
        type="text"
        name="email"
        id="email"
        value={email}
        onChange={e => setEmail(e.target.value)}
      />
    </div>
  )
}

export 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c6zr104a31snvi3e5b09.png)

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.

```jsx
import { useState } from "react"

function App() {
  const [email, setEmail] = useState("")
  return (
    <div className="App">
      <label htmlFor="email">Email:</label>
      <input
        type="text"
        name="email"
        id="email"
        value={email}
        onChange={e => setEmail(e.target.value)}
      />
    </div>
  )
}

export 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:

```jsx
import React, { useRef } from "react"

const UncontrolledComponent = () => {
  const inputRef = useRef()
  const formSubmitHandler = e => {
    e.preventDefault()
    alert("Email: " + inputRef.current.value)
  }
  return (
    <div className="App">
      <form onSubmit={formSubmitHandler}>
        <label htmlFor="email">Email:</label>
        <input type="text" name="email" id="email" ref={inputRef} />
        <input type="submit" value="Submit" />
      </form>
    </div>
  )
}

export 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:

```jsx
import { useState } from "react"

function App() {
  const [email, setEmail] = useState("")
  const [error, setError] = useState("")

  const inputChangeHandler = e => {
    const value = e.target.value
    setEmail(e.target.value)
    if (
      !/[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(
        value
      )
    ) {
      setError("Invalid Email")
    } else {
      setError("")
    }
  }
  return (
    <div className="App">
      <div className="form-control">
        <label htmlFor="email">Email:</label>
        <input
          type="text"
          name="email"
          id="email"
          value={email}
          onChange={inputChangeHandler}
        />
        <p className="error">{error && error}</p>
      </div>
    </div>
  )
}

export 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 to `index.css`:

```css
body {
  margin: 20px auto;
  text-align: center;
}
input,
label {
  margin-right: 5px;
}

.error {
  margin: 5px 0;
  color: red;
}
```

Now if you run the app and type an incorrect email, you should be able to see the error is displayed.

![Invalid email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rwe9xh2kulqoerszclmp.png)

> You can learn more about [form validation in react in one of my previous posts](https://www.codingdeft.com/posts/react-form-validation/).

# Source code

You can download the [source code here](https://github.com/collegewap/react-controlled-vs-uncontrolled).