DEV Community

Cover image for Controlled vs Uncontrolled Components in React
collegewap
collegewap

Posted on • Originally published at codingdeft.com

Controlled vs Uncontrolled Components in React

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:

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
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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:

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

.error {
  margin: 5px 0;
  color: red;
}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)