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.
Say you want a search form that contains different search parameters, namely:
query
: String representing a user's querystatus
: A value taken from a dropdown for either PENDING or APPROVEDcategory
: The category of the search selected from several radio buttons (Possible values: A / B / C)isRegistered
: A boolean value represented by a checkboxThe 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}
The component can now render a form where you bind each of the object params
's attributes to its corresponding element's value
attribute.
For the query
attribute you have:
1<input type="text" value={this.state.params.query} />
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>
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
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} />
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.
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}
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)}/>
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
.
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}
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>
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}
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
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}
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)}/>
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}
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
.