DEV Community

Cover image for controlled forms in react
may arden
may arden

Posted on • Updated on

controlled forms in react

A controlled form is used to submit data to be persisted to a database that derives its input value from state. The alternative to a controlled form is an uncontrolled form, in which the data is handled not by state but, instead, by the DOM. Simply put, in a controlled form, state is aware of the characters being typed into any given form field at any given time. If a controlled form had a parenting style, it would be a helicopter parent, where an uncontrolled form would be a mega-chill and sort of 'hands off' parent, not totally paying attention but still kinda' there.

One good way to tell the difference between a controlled form or uncontrolled form, is to check for value or for defaultValue. If the component has a value prop, it is a controlled component. If it does not have a value prop, it is an uncontrolled component. An uncontrolled component can optionally have a defaultValue prop set to its initial value -- but -- if the component has a value prop, it is a controlled component.

How does it work, you ask? Let's walk through. I arrive at a browser window which is rendering a controlled form. It wants my first name.

The current state looks like this

   state = {
       name: '',
   }
Enter fullscreen mode Exit fullscreen mode

I start to type my name, I hit the letter 'M' on my keyboard. Now, state looks like this

   state = {
       name: 'M',
   }
Enter fullscreen mode Exit fullscreen mode

I continue typing my name, but then I accidentally hit the wrong key. state looks like this now

   state = {
       name: 'Mau',
   }
Enter fullscreen mode Exit fullscreen mode

I realize my error. state looks like this,

   state = {
       name: 'Ma',
   }
Enter fullscreen mode Exit fullscreen mode

and I correct my mistake. state looks like this.

   state = {
       name: 'May',
   }
Enter fullscreen mode Exit fullscreen mode

Before I even press the 'submit' button on the form, state already knows my name. That's accomplished through the following event handler

handleChange = e => {
       const { name, value } = e.target
       this.setState({
           [name]: value
       })
}
Enter fullscreen mode Exit fullscreen mode

Which is invoked inside of the controlled form by the event listener on line five

    <form onSubmit={this.handleSubmit}>
      <label>your name:</label>
      <input type="text">
       value={this.state.name}
       onChange={this.handleChange}
       name="name"/>
      <br/>
      <input type="submit" value="submit"/>
    </form>
Enter fullscreen mode Exit fullscreen mode

These guys work together and say "as soon as there is a change in the form field, we're going to handle that change by passing and destructuring the data provided in the form field and setting it equal to the current state".

Pretty cool.

Now that I've entered my name, I'm ready to hit the 'submit' button. Here we have another event listener & handler ready to send the data from the form field to the database and reset state

handleSubmit = e => {
    e.preventDefault();
    this.props.addPerson(this.state)

    this.setState({
        name: ''
    })
}
Enter fullscreen mode Exit fullscreen mode

This event handler is invoked inside of the controlled form when I press the 'submit' button. My name is then couriered to the database, and state looks like this again now, just like it did at the beginning

   state = {
       name: '',
   }
Enter fullscreen mode Exit fullscreen mode

Why do we do it this way? Controlling forms makes it more convenient to share form values between components. Since the form values are stored in state, they are easily passed down as props or sent upward via a function supplied in props. Another reason controlled forms are preferred is that uncontrolled forms typically yield code that is not DRY. In an uncontrolled form, one may access the form data within the event -- that is to say, the data is available

handleSubmit = e => {
 e.preventDefault()
 const carMake = e.target.children[0].value
 const carModel = e.target.children[1].value
 this.persistDataToDatabase({ carMake, carModel 
 })
}
Enter fullscreen mode Exit fullscreen mode

but, it is just not quite as efficient, or elegant as the alternative, a controlled component.

handleSubmit = e => {
 e.preventDefault()
 this.persistDataToDatabase(this.state)
}
Enter fullscreen mode Exit fullscreen mode

Thanks for reading!

Top comments (0)