Forms

Forms 101

Forms are one of the most crucial parts of our application. While we get some interaction through clicks and mouse moves, it's really through forms where we'll get the majority of our rich input from our users.

In a sense, it's where the rubber meets the road. It's through a form that a user can add their payment info, search for results, edit their profile, upload a photo, or send a message. Forms transform your web site into a web app.

Forms can be deceptively simple. All you really need are some input tags and a submit tag wrapped up in a form tag. However, creating a rich, interactive, easy to use form can often involve a significant amount of programming:

  • Form inputs modify data, both on the page and the server.
  • Changes often have to be kept in sync with data elsewhere on the page.
  • Users can enter unpredictable values, some that we'll want to modify or reject outright.
  • The UI needs to clearly state expectations and errors in the case of validation failures.
  • Fields can depend on each other and have complex logic.
  • Data collected in forms is often sent asynchronously to a back-end server, and we need to keep the user informed of what's happening.
  • We want to be able to test our forms.

If this sounds daunting, don't worry! This is exactly why React was created: to handle the complicated forms that needed to be built at Facebook.

We're going to explore how to handle these challenges with React by building a sign up app. We'll start simple and add more functionality in each step.

Preparation

Inside the code download that came with this book, navigate to forms:


$ cd forms

This folder contains all the code examples for this chapter. To view them in your browser install the dependencies by running npm install (or npm i for short):


$ npm i

Once that's finished, you can start the app with npm start:


$ npm start

You should expect to see the following in your terminal:


$ npm start

Compiled successfully!

The app is running at:

  http://localhost:3000/

You should now be able to see the app in your browser if you go to http://localhost:3000.

This app is powered by Create React App, which we cover in the next chapter.

The Basic Button

At their core, forms are a conversation with the user. Fields are the app's questions, and the values that the user inputs are the answers.

Let's ask the user what they think of React.

We could present the user with a text box, but we'll start even simpler. In this example, we'll constrain the response to just one of two possible answers. We want to know whether the user thinks React is either "great" or "amazing", and the simplest way to do that is to give them two buttons to choose from.

Here's what the first example looks like:


Basic Buttons

To get our app to this stage we create a component with a render() method that returns a div with three child elements: an h1 with the question, and two button elements for the answers. This will look like the following:


  render() {
    return (
      <div>
        <h1>What do you think of React?</h1>

        <button
          name='button-1'
          value='great'
          onClick={this.onGreatClick}
        >
          Great
        </button>

        <button
          name='button-2'
          value='amazing'
          onClick={this.onAmazingClick}
        >
          Amazing
        </button>
      </div>
    );
  }

So far this looks a lot like how you'd handle a form with vanilla HTML. The important part to pay attention to is the onClick prop of the button elements. When a button is clicked, if it has a function set as its onClick prop, that function will be called. We'll use this behavior to know what our user's answer is.

To know what our user's answer is, we pass a different function to each button. Specifically, we'll create function onGreatClick() and provide it to the "Great" button and create function onAmazingClick() and provide it to the "Amazing" button.

Here's what those functions look like:


  onGreatClick = (evt) => {
    console.log('The user clicked button-1: great', evt);
  };

  onAmazingClick = (evt) => {
    console.log('The user clicked button-2: amazing', evt);
  };

When the user clicks on the "Amazing" button, the associated onClick function will run (onAmazingClick() in this case). If, instead, the user clicked the "Great" button, onGreatClick() would be run instead.

Notice that in the onClick handler we pass this.onGreatClick and not this.onGreatClick().

What's the difference?

In the first case (without parens), we're passing the function onGreatClick, whereas in the second case we're passing the result of calling the function onGreatClick (which isn't what we want right now).

This becomes the foundation of our app's ability to respond to a user's input. Our app can do different things depending on the user's response. In this case, we log different messages to the console.

Events and Event Handlers

Note that our onClick functions (onAmazingClick() and onGreatClick()) accept an argument, evt. This is because these functions are event handlers.

Handling events is central to working with forms in React. When we provide a function to an element's onClick prop, that function becomes an event handler. The function will be called when that event occurs, and it will receive an event object as its argument.

In the above example, when the button element is clicked, the corresponding event handler function is called (onAmazingClick() or onGreatClick()) and it is provided with a mouse click event object (evt in this case). This object is a SyntheticMouseEvent. This SyntheticMouseEvent is just a cross-browser wrapper around the browser's native MouseEvent, and you'll be able to use it the same way you would a native DOM event. In addition, if you need the original native event you can access it via the nativeEvent attribute (e.g. evt.nativeEvent).

Event objects contain lots of useful information about the action that occurred. A MouseEvent for example, will let you see the x and y coordinates of the mouse at the time of the click, whether or not the shift key was pressed, and (most useful for this example) a reference to the element that was clicked. We'll use this information to simplify things in the next section.

Instead, if we were interested in mouse movement, we could have created an event handler and provided it to the onMouseMove prop. In fact, there are many such element props that you can provide mouse event handlers to:, onClick, onContextMenu, onDoubleClick, onDrag, onDragEnd, onDragEnter, onDragExit, onDragLeave, onDragOver, onDragStart, onDrop, onMouseDown, onMouseEnter, onMouseLeave, onMouseMove, onMouseOut, onMouseOver, and onMouseUp.

And those are only the mouse events. There are also clipboard, composition, keyboard, focus, form, selection, touch, ui, wheel, media, image, animation, and transition event groups. Each group has its own types of events, and not all events are appropriate for all elements. For example, here we will mainly work with the form events, onChange and onSubmit, which are related to form and input elements.

For more information on events in React, see React's documentation on the Event System.

Back to the Button

In the previous section, we were able to perform different actions (log different messages) depending on the action of the user. However, the way that we set it up, we'd need to create a separate function for each action. Instead, it would be much cleaner if we provided the same event handler to both buttons, and used information from the event itself to determine our response.

To do this, we replace the two event handlers onGreatClick() and onAmazingClick() with a new single event handler, onButtonClick().


  onButtonClick = (evt) => {
    const btn = evt.target;
    console.log(`The user clicked ${btn.name}: ${btn.value}`);
  };

Our click handler function receives an event object, evt. evt has an attribute target that is a reference to the button that the user clicked. This way we can access the button that the user clicked without creating a function for each button. We can then log out different messages for different user behavior.

Next we update our render() function so that our button elements both use the same event handler, our new onButtonClick() function.


  render() {
    return (
      <div>
        <h1>What do you think of React?</h1>

        <button
          name='button-1'
          value='great'
          onClick={this.onButtonClick}
        >
          Great
        </button>

        <button
          name='button-2'
          value='amazing'
          onClick={this.onButtonClick}
        >
          Amazing
        </button>
      </div>
    );
  }


One Event Handler for Both Buttons

By taking advantage of the event object and using a shared event handler, we could add 100 new buttons, and we wouldn't have to make any other changes to our app.

Text Input

In the previous example, we constrained our user's response to only one of two possibilities. Now that we know how to take advantage of event objects and handlers in React, we're going to accept a much wider range of responses and move on to a more typical use of forms: text input.

To showcase text input we'll create a "sign-up sheet" app. The purpose of this app is to allow a user to record a list of names of people who want to sign up for an event.

The app presents the user a text field where they can input a name and hit "Submit". When they enter a name, it is added to a list, that list is displayed, and the text box is cleared so they can enter a new name.

Here's what it will look like:


Sign-Up Adding to a List

Accessing User Input With refs

We want to be able to read the contents of the text field when the user submits the form. A simple way to do this is to wait until the user submits the form, find the text field in the DOM, and finally grab its value.

To begin we'll start by creating a form element with two child elements: a text input field and a submit button:


  render() {
    return (
      <div>
        <h1>Sign Up Sheet</h1>

        <form onSubmit={this.onFormSubmit}>
          <input
            placeholder='Name'
            ref='name'
          />

          <input type='submit' />
        </form>
      </div>
    );
  }

This is very similar to the previous example, but instead of two button elements, we now have a form element with two child elements: a text field and a submit button.

There are two things to notice. First, we've added an onSubmit event handler to the form element. Second, we've given the text field a ref prop of 'name'.

By using an onSubmit event handler on the form element this example will behave a little differently than before. One change is that the handler will be called either by clicking the "Submit" button, or by pressing "enter"/"return" while the form has focus. This is more user-friendly than forcing the user to click the "Submit" button.

However, because our event handler is tied to the form, the event object argument to the handler is less useful than it was in the previous example. Before, we were able to use the target prop of the event to reference the button and get its value. This time, we're interested in the text field's value. One option would be to use the event's target to reference the form and from there we could find the child input we're interested in, but there's a simpler way.

In React, if we want to easily access a DOM element in a component we can use refs (references). Above, we gave our text field a ref property of 'name'. Later when the onSubmit handler is called, we have the ability to access this.refs.name to get a reference to that text field. Here's what that looks like in our onFormSubmit() event handler:


  onFormSubmit = (evt) => {
    evt.preventDefault();
    console.log(this.refs.name.value);
  };

Use preventDefault() with the onSubmit handler to prevent the browser's default action of submitting the form.

As you can see, by using this.refs.name we gain a reference to our text field element and we can access its value property. That value property contains the text that was entered into the field.


Logging The Name

With just the two functions render() and onFormSubmit(), we should now be able to see the value of the text field in our console when we click "Submit". In the next step we'll take that value and display it on the page.

Using User Input

Now that we've shown that we can get user submitted names, we can begin to use this information to change the app's state and UI.

The goal of this example is to show a list with all of the names that the user has entered. React makes this easy. We will have an array in our state to hold the names, and in render() we will use that array to populate a list.

When our app loads, the array will be empty, and each time the user submits a new name, we will add it to the array. To do this, we'll make a few additions to our component.

First, we'll create a names array in our state. In React, when we're using ES6 component classes we can set the initial value of our state object by defining a property of state.

Here's what that looks like:


module.exports = class extends React.Component {
  static displayName = "04-basic-input";
  state = { names: [] }; // <-- initial state

static belongs to the class

Notice in this component we have the line:


static displayName = "04-basic-input";

This means that this component class has a static property displayName. When a property is static, that means it is a class property (instead of an instance property). In this case, we're going to use this displayName when we show the list of examples on the demo listing page.

Next, we'll modify render() to show this list. Below our form element, we'll create a new div. This new container div will hold a heading, h3, and our names list, a ul parent with a li child for each name. Here's our updated render() method:


  render() {
    return (
      <div>
        <h1>Sign Up Sheet</h1>

        <form onSubmit={this.onFormSubmit}>
          <input
            placeholder='Name'
            ref='name'
          />

          <input type='submit' />
        </form>

        <div>
          <h3>Names</h3>
          <ul>
            { this.state.names.map((name, i) => <li key={i}>{name}</li>) }
          </ul>
        </div>
      </div>
    );
  }

ES2015 gives us a compact way to insert li children. Since this.state.names is an array, we can take advantage of its map() method to return a li child element for each name in the array. Also, by using "arrow" syntax, for our iterator function in map(), the li element is returned without us explicitly using return.

One other thing to note here is that we provide a key prop to the li element. React will complain when we have children in an array or iterator (like we do here) and they don't have a key prop. React wants this information to keep track of the child and make sure that it can be reused between render passes.

We won't be removing or reordering the list here, so it is sufficient to identify each child by its index. If we wanted to optimize rendering for a more complex use-case, we could assign an immutable id to each name that was not tied to its value or order in the array. This would allow React to reuse the element even if its position or value was changed.

See React's documentation on Multiple Components and Dynamic Children for more information.