Different Ways of Handling Forms in React

There are two types of form input in react. We have the uncontrolled input and the controlled input. The uncontrolled input are like traditional HTML form inputs, they remember what you typed. We will use ref to get the form values.

Travel form Challenge
You just started your own airline, and you need to create a form to collect data about your passengers' upcoming travel plans so they can book their flight.
You should collect the following information from the user:
First name (text input)
Last name (text input)
Age (number input)
Message (textarea)
Gender (radio buttons with 2 or more options)
Location they're traveling to (select box with at least 3 options. You're an airline that doesn't fly to many places...)
• Whether they have any dietary restrictions (check boxes for vegetarian, kosher, lactose free, etc. Include at least 3 options)

Each element of the form should be given a name attribute so you can access the data in JavaScript and do stuff with it. There should also be a button at the end of the form to submit it.

This exercise is adapted from the V School curriculum on vanilla JS forms:
https://coursework.vschool.io/travel-form

Remember to use the concept of controlled forms https://reactjs.org/docs/forms.html and uncontrolled forms(Refs and the DOM) https://reactjs.org/docs/refs-and-the-dom.html

#1 A controlled input form
The controlled input is when the react component that renders a form also controls what happens in that form on subsequent user input. Meaning, as form value changes, the component that renders the form saves the value in its state.

Controlled components are heavy duty. The defining characteristic of a controlled component is the displayed value is bound to component state; to update the value you execute a function attached to the onChange event handler on the form element. The onChange function updates the state property, which in turn updates the form element's value.
Fig: React Controlled form handling

import React, {Component} from "react"
class ControlledForms extends Component {
    constructor() {
        super()
        this.state = {
            firstName: "",
            lastName: "",
            age: "",
            message: "",
            gender: "",
            destination: "",
            isVegan: false,
            isKosher: false,
            isLactoseFree: false
        }
        this.handleChange = this.handleChange.bind(this)
    }
    
    handleChange(event) {
        const {name, value, type, checked} = event.target
        type === "checkbox" ? 
            this.setState({
                [name]: checked
            })
        :
        this.setState({
            [name]: value
        }) 
    }
    
    render() {
        return (
            <main className="container">
                <form>
                    <input 
                        name="firstName" 
                        value={this.state.firstName} 
                        onChange={this.handleChange} 
                        placeholder="First Name" 
                    />
                    <br />
                    
                    <input 
                        name="lastName" 
                        value={this.state.lastName}
                        onChange={this.handleChange} 
                        placeholder="Last Name" 
                    />
                    <br />
                    
                    <input 
                        name="age" 
                        value={this.state.age}
                        onChange={this.handleChange} 
                        placeholder="Age" 
                    />
                    <br />

                    <label>
                        <textarea
                            name="message"
                            onChange={this.handleChange}
                            placeholder="Enter message"
                            value={this.state.message}
                        >
                        </textarea>
                    </label>
                    <br />

                    <label>
                        <input 
                            type="radio" 
                            name="gender"
                            value="male"
                            checked={this.state.gender === "male"}
                            onChange={this.handleChange}
                        /> Male
                    </label>
                    
                    <br />
                    
                    <label>
                        <input 
                            type="radio" 
                            name="gender"
                            value="female"
                            checked={this.state.gender === "female"}
                            onChange={this.handleChange}
                        /> Female
                    </label>
                    
                    <br />
                    
                    <select 
                        value={this.state.destination} 
                        name="destination" 
                        onChange={this.handleChange}
                    >
                        <option value="">-- Please Choose a destination --</option>
                        <option value="Sweden">Sweden</option>
                        <option value="Norway">Norway</option>
                        <option value="Denmark">Denmark</option>
                        <option value="Finland">Finland</option>
                    </select>
                    
                    <br />
                    
                    <label>
                        <input 
                            type="checkbox"
                            name="isVegan"
                            onChange={this.handleChange}
                            checked={this.state.isVegan}
                        /> Vegan?
                    </label>
                    <br />
                    
                    <label>
                        <input 
                            type="checkbox"
                            name="isKosher"
                            onChange={this.handleChange}
                            checked={this.state.isKosher}
                        /> Kosher?
                    </label>
                    <br />
                    
                    <label>
                        <input 
                            type="checkbox"
                            name="isLactoseFree"
                            onChange={this.handleChange}
                            checked={this.state.isLactoseFree}
                        /> Lactose Free?
                    </label>
                    <br />
                    
                    <button>Submit</button>
                </form>
                <hr />
                <h2>Entered information:</h2>
                <p>Your name: {this.state.firstName} {this.state.lastName}</p>
                <p>Your age: {this.state.age}</p>
                <p>Your Message: {this.state.message}</p>
                <p>Your gender: {this.state.gender}</p>
                <p>Your destination: {this.state.destination}</p>
                <p>Your dietary restrictions:</p>
                
                <p>Vegan: {this.state.isVegan ? "Yes" : "No"}</p>
                <p>Kosher: {this.state.isKosher ? "Yes" : "No"}</p>
                <p>Lactose Free: {this.state.isLactoseFree ? "Yes" : "No"}</p>
                
            </main>
        )
    }
}

export default ControlledForms

The goal is that each time the input changes, the method handleChange is called and will store the input state. Hence the component always has the current value of the input without needing to ask for it. This means that the form component can respond to input changes immediately.

To be able to handle multiple form inputs, we need a way to capture the input with a method instead of declaring multiple methods to do this. Hence we are creating dynamic handleChange method so that our form input can reference it to update it states dynamically.

The value of the name attribute on each input must be the same with the state name declared in the formControls in the constructor.

#2 A uncontrolled input form
An easier, and less labor-intensive way to grab values from a form element is to use the ref property in the form itself.
import React, {Component} from "react"
class UncontrolledForms extends Component {

    handleSubmit = (event) => {
        event.preventDefault();
        const { firstName, lastName, age, message, gender, destination, dietary_restriction } = this.form;

        // convert node list to an array
        const dietaryRestrictionCheckboxArray = Array.prototype.slice.call(dietary_restriction);
        // extract only the checked checkboxes
        const dietaryCheckedCheckboxes = dietaryRestrictionCheckboxArray.filter(input => input.checked);
        // use .map() to extract the value from each checked checkbox
        const dietaryCheckedCheckboxesValue = dietaryCheckedCheckboxes.map(input => input.value);

        const data = {
            firstName: firstName.value,
            lastName: lastName.value,
            age: age.value,
            message: message.value,
            gender: gender.value,
            destination: destination.value,
            dietaryOptions: dietaryCheckedCheckboxesValue
        }

        //alert(JSON.stringify(data));
        console.log(data);
    }
    
    render() {
        return (
            <main className="container">
                <form onSubmit={this.handleSubmit} ref={myform => this.form = myform}>
                    <input name="firstName" placeholder="First Name" />
                    <br />
                    
                    <input name="lastName" placeholder="Last Name" />
                    <br />
                    
                    <input name="age" placeholder="Age" />
                    <br />

                    <textarea name="message" placeholder="Enter message"></textarea>
                    <br />

                    <input type="radio" name="gender" value="male"/> Male
                    <br />
                    <input type="radio" name="gender" value="female"/> Female
                    <br />

                    <select name="destination">
                        <option value="">-- Please Choose a destination --</option>
                        <option value="Sweden">Sweden</option>
                        <option value="Norway">Norway</option>
                        <option value="Denmark">Denmark</option>
                        <option value="Finland">Finland</option>
                    </select>
                    <br />
                    
                    <input type="checkbox" value="vegan" name="dietary_restriction"/>Vegan?
                    <br />
                    <input type="checkbox" value="kosher" name="dietary_restriction"/>Kosher?
                    <br />
                    <input type="checkbox" value="lactoseFree" name="dietary_restriction"/>Lactose Free?
                    <br />
                    <button>Submit</button>
                </form>
                <hr />
            </main>
        )
    }
}

export default UncontrolledForms



Different ways of using refs on form elements
Different form elements and component compositions require different strategies, so the rest of this post is divided into the following sections.

i. Text & number inputs, selects
Text and number inputs provide the most straightforward example of using refs. In the ref attribute of the input, add an arrow function that takes the input as an argument.
<input  
  type="text"
  ref={input => this.fullName = input} />
Since it's an alias for the input element itself, you can name the argument whatever you'd like:
<input  
  type="number"
  ref={cashMoney => this.amount = cashMoney} />
ii. Radio sets
Unlike text and number input elements, radios come in sets. Each element in a set has the same name attribute, like so:
There are two options in the gender radio set – 'male' and 'female'.

Since the whole set is the object of our concern, setting a ref on each radio input is not ideal. And, unfortunately, there's no DOM node that encapsulates a set of radios.

Retrieving the value of the radio set can be obtained through three steps:
1. Set a ref on the <form> tag
2. Extract the set of radios from the form. In this case, it is the gender set
3. Grab the value of the set using dot notation

iii. Checkbox sets
Unlike a radio set, a checkbox set may have multiple values selected. This makes extracting these values a little more complicated than extracting the value of a radio set.

Retrieving the selected values of the checkbox set can be done through these five steps:
1. Set a ref on the <form> tag.
2. Extract the set of checkboxes from the form.
3. Convert the node list to an array, so array methods are available.
4. Use Array.filter() to grab only the checked checkboxes.
5. Use Array.map() to keep only the values of the checked checkboxes.


The primary value of using refs over controlled component is that, in most cases, you will write less code. The exceptional case is that of checkbox sets (and radios to a lesser degree). Source: Learn React for free
Travel Form Challenge
React.js Forms: Using Refs
The complete guide to Forms in React

Comments

Popular Posts