Author avatar

Raphael Alampay

Two-Way Form Binding with React

Raphael Alampay

  • Sep 18, 2020
  • 9 Min read
  • 506 Views
  • Sep 18, 2020
  • 9 Min read
  • 506 Views
Web Development
Client-side Frameworks
React
Front End Web Development

Introduction

A JavaScript object is often bound to an HTML form where each input element reflects the value of an object's attribute. Two-way binding refers to updating the form-bound object's attributes' values with every change made by the user in the form. In this guide, we'll take a look at how to bind an object to a form that dynamically changes as input changes.

Object: Search Parameters

Say you want a search form that contains different search parameters, namely:

  • query: String representing a user's query
  • status: A value taken from a dropdown for either PENDING or APPROVED
  • category: The category of the search selected from several radio buttons (Possible values: A / B / C)
  • isRegistered: A boolean value represented by a checkbox

Defining the Object as a State

The search parameters can be represented as an object called params with default values. This object will be set as an attribute of state in the React component SearchForm under its constructor.

1import React from 'react';
2
3export default class SearchForm extends React.Component {
4  constructor(props) {
5    super(props);
6
7    this.state = {
8      params: {
9        query: "",
10        status: "PENDING",
11        category: "A",
12        isRegistered: false
13      }
14    }
15  }
16}
javascript

Mapping Object to Form

The component can now render a form where you bind each of the object params's attributes to its corresponding element's value attribute.

Text Input

For the query attribute you have:

1<input type="text" value={this.state.params.query} />
jsx

Select / Dropdown Box

The dropdown for status is similar in the sense that you also bind it to the value parameter.

1<select value={this.state.params.status}>
2  <option value='PENDING'>Pending</option>
3  <option value='APPROVED'>Pending</option>
4</select>
jsx

Radio Button

Radio buttons work a little bit differently. The values are hardcoded for each selection in the group. A radio button is checked if the current value for category is equal to its hard-coded value.

1<input type="radio" value="A" checked={this.state.params.category === "A"}/> Category A
2<input type="radio" value="B" checked={this.state.params.category === "B"}/> Category B
3<input type="radio" value="C" checked={this.state.params.category === "C"}/> Category C
jsx

Checkbox

Similar to radio buttons, a checkbox's checked state given by the checked attribute is set to either true or false depending on the bound value from the params object (in this case isRegistered):

1<input type="checkbox" checked={this.state.params.isRegistered} />
jsx

Binding Functions

The logic behind two-way binding happens with an event handler function attached to the input element. This function defines the logic in terms of getting the value of the input element it's bound to and updating the component's state. The signature of the function will have an event argument passed to it that contains a target property that references the input element. From target, we can then get the input element's current value by accessing its value property.

Text Input

The event handler function for query might look like the following:

1handleQueryChanged(event) {
2  var params    = this.state.params;  // Extract the current params object from state
3  params.query  = event.target.value; // Get the value from the event's target reference
4
5  // Update the state of the component
6  this.setState({
7    params: params
8  });
9}
javascript

Bind the function to the onChange attribute of the input element so it will be triggered every time the user modifies it:

1<input type="text" value={this.state.params.query} onChange={this.handleQueryChanged.bind(this)}/>
jsx

Notice how the code invokes .bind(this) to the function. The purpose is to have this retain its value, that is, a reference to the instance of the component, allowing the logic within the function to always refer to the component—in this case, calling the component's setState() method from this.

Select/Dropdown Box

Do the same for the select element's event handler function:

1handleStatusChanged(event) {
2  var params    = this.state.params;
3  params.status = event.target.value;
4
5  this.setState({
6    params: params
7  });
8}
javascript

Bind the function to the input element:

1<select value={this.state.params.status} onChange={this.handleStatusChanged.bind(this)}>
2  <option value='PENDING'>Pending</option>
3  <option value='APPROVED'>Pending</option>
4</select>
jsx

Radio Button

All three radio buttons for the category will have the same event handler function:

1handleCategoryChanged(event) {
2  var params      = this.state.params;
3  params.category = event.target.value;
4
5  this.setState({
6    params: params
7  });
8}
javascript

Bind the function to each element accordingly:

1<input type="radio" value="A" checked={this.state.params.category === "A"} onChange={this.handleCategoryChanged.bind(this)}/> Category A
2<input type="radio" value="B" checked={this.state.params.category === "B"} onChange={this.handleCategoryChanged.bind(this)}/> Category B
3<input type="radio" value="C" checked={this.state.params.category === "C"} onChange={this.handleCategoryChanged.bind(this)}/> Category C
jsx

Checkbox

The event handler for the checkbox is also similar.

1handleIsRegisteredChanged(event) {
2  var params          = this.state.params;
3  params.isRegistered = event.target.value;
4
5  this.setState({
6    params: params
7  });
8}
javascript

Hooking it up to the input element follows the same pattern.

1<input type="checkbox" checked={this.state.params.isRegistered} onChange={this.handleIsRegisteredChanged.bind(this)}/>
jsx

Putting it All Together

The final code will include the functions to update the params state as well as the rendered form. Include a console.log(this.state.params) line before the render function returns in order to monitor the object's current values. These values should change every time the this.setState() method is invoked.

1import React from 'react';
2
3export default class SearchForm extends React.Component {
4  constructor(props) {
5    super(props);
6
7    this.state = {
8      params: {
9        query: "",
10        status: "PENDING",
11        category: "A",
12        isRegistered: false
13      }
14    }
15  }
16
17  handleQueryChanged(event) {
18    var params    = this.state.params;  // Extract the current params object from state
19    params.query  = event.target.value; // Get the value from the event's target reference
20
21    // Update the state of the component
22    this.setState({
23      params: params
24    });
25  }
26
27  handleStatusChanged(event) {
28    var params    = this.state.params;
29    params.status = event.target.value;
30
31    this.setState({
32      params: params
33    });
34  }
35
36  handleCategoryChanged(event) {
37    var params      = this.state.params;
38    params.category = event.target.value;
39
40    this.setState({
41      params: params
42    });
43  }
44
45  handleIsRegisteredChanged(event) {
46    var params          = this.state.params;
47    params.isRegistered = event.target.value;
48
49    this.setState({
50      params: params
51    });
52  }
53
54  render() {
55    // Examine the current values of params on every render
56    console.log(this.state.params);
57
58    return (
59      <div>
60        <label>Query:</label>
61        <input type="text" value={this.state.params.query} onChange={this.handleQueryChanged.bind(this)}/>
62
63        <br/>
64
65        <label>Status:</label>
66        <select value={this.state.params.status} onChange={this.handleStatusChanged.bind(this)}>
67          <option value='PENDING'>Pending</option>
68          <option value='APPROVED'>Pending</option>
69        </select>
70
71        <br/>
72
73        <label>Category:</label>
74        <input type="radio" value="A" checked={this.state.params.category === "A"} onChange={this.handleCategoryChanged.bind(this)}/> Category A
75        <input type="radio" value="B" checked={this.state.params.category === "B"} onChange={this.handleCategoryChanged.bind(this)}/> Category B
76        <input type="radio" value="C" checked={this.state.params.category === "C"} onChange={this.handleCategoryChanged.bind(this)}/> Category C
77
78        <br/>
79        <input type="checkbox" checked={this.state.params.isRegistered} onChange={this.handleIsRegisteredChanged.bind(this)}/> Is Registered?
80
81      </div>
82    );
83  }
84}
javascript

Conclusion

Two-way binding in React.js can be achieved by providing a state object in a component and updating its values through event handler functions bound to input elements. These input elements are mapped to each of the object's attributes, creating a binding effect that constantly updates the object as a user modifies the input. When ready to be submitted to an API endpoint, the only thing left to do is to reference the current object's state from the component's state.