Skip to content

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Two-Way Form Binding with React

In this guide, we'll take a look at how to bind a JavaScript object to an HTML form that dynamically changes as input changes.

Sep 18, 2020 • 9 Minute Read

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.

      import React from 'react';

export default class SearchForm extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      params: {
        query: "",
        status: "PENDING",
        category: "A",
        isRegistered: false
      }
    }
  }
}
    

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:

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

Select / Dropdown Box

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

      <select value={this.state.params.status}>
  <option value='PENDING'>Pending</option>
  <option value='APPROVED'>Pending</option>
</select>
    

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.

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

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):

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

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:

      handleQueryChanged(event) {
  var params    = this.state.params;  // Extract the current params object from state
  params.query  = event.target.value; // Get the value from the event's target reference

  // Update the state of the component
  this.setState({
    params: params
  });
}
    

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

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

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:

      handleStatusChanged(event) {
  var params    = this.state.params;
  params.status = event.target.value;

  this.setState({
    params: params
  });
}
    

Bind the function to the input element:

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

Radio Button

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

      handleCategoryChanged(event) {
  var params      = this.state.params;
  params.category = event.target.value;

  this.setState({
    params: params
  });
}
    

Bind the function to each element accordingly:

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

Checkbox

The event handler for the checkbox is also similar.

      handleIsRegisteredChanged(event) {
  var params          = this.state.params;
  params.isRegistered = event.target.value;

  this.setState({
    params: params
  });
}
    

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

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

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.

      import React from 'react';

export default class SearchForm extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      params: {
        query: "",
        status: "PENDING",
        category: "A",
        isRegistered: false
      }
    }
  }

  handleQueryChanged(event) {
    var params    = this.state.params;  // Extract the current params object from state
    params.query  = event.target.value; // Get the value from the event's target reference

    // Update the state of the component
    this.setState({
      params: params
    });
  }

  handleStatusChanged(event) {
    var params    = this.state.params;
    params.status = event.target.value;

    this.setState({
      params: params
    });
  }

  handleCategoryChanged(event) {
    var params      = this.state.params;
    params.category = event.target.value;

    this.setState({
      params: params
    });
  }

  handleIsRegisteredChanged(event) {
    var params          = this.state.params;
    params.isRegistered = event.target.value;

    this.setState({
      params: params
    });
  }

  render() {
    // Examine the current values of params on every render
    console.log(this.state.params);

    return (
      <div>
        <label>Query:</label>
        <input type="text" value={this.state.params.query} onChange={this.handleQueryChanged.bind(this)}/>

        <br/>

        <label>Status:</label>
        <select value={this.state.params.status} onChange={this.handleStatusChanged.bind(this)}>
          <option value='PENDING'>Pending</option>
          <option value='APPROVED'>Pending</option>
        </select>

        <br/>

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

        <br/>
        <input type="checkbox" checked={this.state.params.isRegistered} onChange={this.handleIsRegisteredChanged.bind(this)}/> Is Registered?

      </div>
    );
  }
}
    

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.