Section 7 - React Forms

React Forms Introduction

  1. Forms, such as login forms, register forms and search bars, are a super common element in websites. Creating forms in React is surprisingly complex. That’s why we’ll spend an entire section to look into React forms. Let’s get started!

Once again, let’s clear up everything in App.js and start fresh:

import React, { Component } from 'react';

class App extends Component {
 render() {
   return (
   )
 }
}

export default App;

  1. Let’s render a simple <input> element!

render() {

  return <input type="text"></input>;

}

  1. Check out the input field in the browser. It is underwhelming, but nonetheless it is working. Now, how are we going to access the input value? One way of doing so is using ref. Ref, short for reference, is a special attribute that allows us to access DOM elements like so:

render() {

  return <input type="text" ref={input => (this.inputField = input)}/>;

}

Ref takes in a function. The function has one parameter passed to it, which is the current DOM element (in our case, our <input> element). Then, we store the DOM element into the React class as an variable called inputField. We can read the value of the input field by reading inputField’s value attribute. This means in our code we can read from this.inputField.value whenever necessary! Let’s give it a try! We now write more JSX to simulate a real form:

render() {

  return (

    <div>

      <input type="text" ref={input => (this.inputField = input)}/>

      <button>Submit</button>

    </div>

  );

}

  1. When the submit button is clicked, we want something to happen. Let’s say we want to console.log whatever is being typed in the input field. Give it a go yourself!

Answer:

import React, { Component } from 'react';

class App extends Component {

  processInput(){

    console.log(this.inputField.value);

  }

  render() {

    return (

      <div>

        <input type="text" ref={input => (this.inputField = input)}/>

        <button onClick={this.processInput.bind(this)}>Submit</button>

      </div>

    );

  }

}

export default App;

Here I wrote a function processInput(), which simply prints this.inputField.value. The function is called whenever the submit button is clicked. I did this by passing processInput() as an event handler into the onClick property of <button>. Not too hard right?

  1. What if now we want to add a default value to the input? Let’s try doing so!

<input

  type="text"

  value="Default Value"

  ref={input => (this.inputField = input)}

/>

  1. Let’s go back to our browser. Cool! “Default Value” is there! But when you try to type in the input field. It doesn’t change. Wait what? This is because, whenever you make changes to the input field, let’s say you type in an “a”, so that the input field now contains “Default Valuea”, React will notice that “Default Valuea” isn’t equal to the value we provided - “Default Value”. This triggers the render() function, and the input field is quickly changed back to “Default Value” again.

In addition, you’ll find a new warning message in the browser's console:

Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler...

  1. Just as the message suggested, the way to solve this is to make use of the onChange event listener. Now I’ll introduce a new pattern for creating React forms that doesn’t involve ref anymore. It’s slightly complex, but it’s a very common way of creating React forms. So here we go:

  1. First, we make use of state to store our input value like so:

state = {

  value: ""

}

We initialize it to an empty string here, but you can replace it with whatever default value you want.

  1. Next, in our <input> element, we get rid of the ref property and set the “value” property to our state:

<input

  type="text"

  ref={...}

  value={this.state.value}

/>

  1. Next, as we mentioned above, we need to write an event handler function for the onChange event listener. The function will be called every time a change is made to the input value. It will modify state to reflect the change, then React will re-render the <input> element with the new value shown in our browser.

We can write an arrow function like:

<input

  type="text"

  value={this.state.value}

  onChange={(e)=>{

      this.setState({

        value: e.target.value

      })

  }}

/>

An event object e is being passed into the event handler function. It contains an attribute target, which refers to the element that evoked the event (in this case, our <input> element). Then, we read the value of the <input> element and set it to the our state.

  1. Then, in our processInput() function, we can read from this.state.value like so:

processInput(){

  console.log(this.state.value);

}

Now try the form out and everything should be the same. Did we do all this work for nothing? Not exactly. By having the value stored in our state, and by using the onChange event handler, we now have the ability to implement much more powerful functionalities!

Uppercase Enforcement Example

  1. Continuing from the last part, let’s say we now want to ensure that the input field only contains uppercase letters. How would you do that? Try it out yourself!

Answer:

<input

  type="text"

  value={this.state.value}

  onChange={(e)=>{

    this.setState({

      value: e.target.value.toUpperCase()

    })

  }}

/>

It is actually embarrassingly simple! All I did was to use toUpperCase() to turn our string to uppercase before setting it to our state!

Password Validation Example

  1. Let’s say this time, we have a password field. We require the password to be (1) at least 8 characters long and (2) have at least one uppercase letter. How would we do that? Let’s first add a boolean value valid to our state to indicate if our password if valid or not:

state = {

  value: "",

  valid: false

}

  1. Now in our render function, we can render appropriate messages according to the validity of the input password:

{ this.state.valid ? "Valid Password!" : "Invalid Password!" }

  1. Then, we could modify our event handler function like so:

<input

  type="text"

  value={this.state.value}

  onChange={(e)=>{

    const value = e.target.value;

    let valid;

    if(value.length >= 8 && value.replace(/[^A-Z]/g, "").length >= 1){

      valid = true;

    } else {

      valid = false;

    }

    this.setState({

      value,

      valid

    })

  }}

/>

  1. In the if statement, value.length >= 8 checks if the input password has at least 8 characters, and value.replace(/[^A-Z]/g, "").length >= 1 checks if the input password has at least one uppercase character (code from this forum thread). Then, we assign the appropriate boolean value to the valid variable. Finally, we use setState to set the values of value and valid. By the way,

{

      value,

      valid

}

Is just an ES6 shorthand syntax for

{

      value: value,

      valid: valid

}

Now, go to the browser and type out different passwords. You’ll see, for example, “react123” and “React12” are invalid passwords, but “React123” is valid! See how convenient it is to always have the value of the input field in our state?

Summary

In this section, we explored two ways of creating React forms.

First, we made use of ref to get a reference to the <input> DOM element in order to read its value. This approach is easier, but has limited functionalities besides just reading the input field’s value.

Second, we learnt about a new pattern using state to store the input field’s value, then updating the state with an onChange event handler. Having the input field’s value in our state all the time allowed us to implement a lot of useful functionalities.

Finally, this stackoverflow thread gives examples of how state is more useful than ref, explains why the use of ref is generally discouraged and explains when it is appropriate to use ref.

Final Code

Here is the code for this section.