Every web app has at least one form with multiple inputs. The most common example is a simple signup or user registration form with multiple input fields for name, email, password, etc. In React's component architecture, the entire form is a controlled component with its state managing the form data through various event handlers. This guide explores the cleanest and most efficient way of handling multiple inputs using a single JavaScript function in React.
The onChange
event watches for changes in an input field. Every time the user types something into the field, the onChange
event is triggered. Since the value of a field is dynamic, this event comes in handy for form fields where each new character entered into the field affects what is shown to a user, such as in a signup form that tells a user whether a username is available or not.
After the onChange event has been set to watch for changes in an input field, the next step is to store that dynamic data somewhere. An event handler
, or a simple JavaScript function, is invoked every time the event is triggered and the data is extracted using the event object
.
The event object contains a property called target
. This target
property has the ID of that input field attached as a property itself. If every input field has a unique ID, the event object can be manipulated to directly get the key-value pair that needs to be stored inside the state using e.target.id
, where e
refers to the event object and e.target.value
is simply the extracted value from the input field. To make this work, the state variables need to have the same name as the ID.
Here's how to implement this solution.
Make sure you have Nodejs
and npm
installed on your machine (at least version 8 or higher), along with a code editor and a web browser (preferably Chrome or Firefox).
Create a new project using create-react-app
:
1npx create-react-app onchange-handler-app
Some basic form and button styles inside index.css
:
1body {
2 margin: 0;
3 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 sans-serif;
6 -webkit-font-smoothing: antialiased;
7 -moz-osx-font-smoothing: grayscale;
8}
9
10code {
11 font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 monospace;
13}
14
15.form-container{
16 position: absolute;
17 top: 100px;
18 left: 280px;
19}
20
21
22input[type=text], select {
23 width: 100%;
24 padding: 22px 20px;
25 margin: 18px 0;
26 display: inline-block;
27 border: 1px solid #ccc;
28 border-radius: 6px;
29}
30
31button{
32 width: 100px;
33 height: 40px;
34 background-color: rgb(156, 156, 204);
35 color: #ffffff;
36 border: 2px solid rgb(156, 156, 204);
37 border-radius: 20px;
38 cursor: pointer;
39}
Create a class component inside the root directory with the name Form.js
. This component contains a form with four different input fields: Name
, Email
, Country
, and City
of residence. The state of the form component, therefore, has four key-value pairs corresponding to each field. Each field has an onChange event listener
attached to it which takes in the event object
, grabs the data in that field, and calls the asynchronous setState()
method to update the state. At the end of the form, the Submit button, on clicking, renders the data stored in the state on the DOM.
1import React, {Component} from 'react';
2
3export default class Form extends Component{
4 state={
5 name: '',
6 email:'',
7 country:'',
8 city:''
9 }
10 handleChange=(e)=>{
11 this.setState({
12 [e.target.id]:e.target.value
13 },()=>{
14 console.log(this.state);
15 })
16
17 }
18 handleSubmit=()=>{
19 console.log(this.state)
20 let details=document.querySelector('#details');
21 details.textContent=this.state.name+' '+
22 this.state.email+' '+
23 this.state.country+' '+
24 this.state.city;
25 }
26 render(){
27 return(
28 <div className="form-container">
29 <form action="">
30 <input type="text" id="name" placeholder="name" onChange={this.handleChange}/>
31 <input type="text" id="email" placeholder="email" onChange={this.handleChange}/>
32 <input type="text" id="country" placeholder="country" onChange={this.handleChange}/>
33 <input type="text" id="city" placeholder="city" onChange={this.handleChange}/>
34
35 </form>
36 <button onClick={this.handleSubmit}>Submit</button>
37 <div className="details">
38 <p>User Details:</p>
39 <span id="details"></span>
40 </div>
41 </div>
42 )
43
44 }
45}
An object's property can be accessed using dot notation or square bracket notation. Since each input field contains a unique ID, [e.target.id]
matches the key name of the state's property. Hence, the state variable is updated by the onChange handler
without directly knowing which input field was supposed to update which state variable. You can see that console.log()
in each method verifies that the state is updated dynamically whenever the user types into an input field.
The code above will generate a UI that looks like this:
Run npm start
to spin a local development server. You can see the app up and running on localhost:3000 in your browser (create-react-app
automatically does this for you ). Open the developer console and start filling out the form fields. Data for each field is outputted on the console dynamically, and the state is updated in real time. Fill out all the fields and click the Submit button. User details are rendered to the DOM from the state of the component.
The number of lines of code is fewer, setState()
is called only once, and the code appears cleaner therefore easy to debug. However, there is still room for improvement. The function calls to the event handlers are directly proportional to the number of input fields, and since every input field is calling the same function, it is redundant to call handleChange
multiple times and occupy so much space on the function call stack.
Almost all events in JavaScript bubble. The event on the input field runs through its handlers and subsequently through its parent's handler. In this example, the form
tag wraps all input
tags so the onChange event
can watch for changes directly to the entire form rather than every individual input field.
Inside Form.js
, remove all invocations of handleChange
and call this function from the form
tag itself. Due to event bubbling, whenever an onChange event
triggers in any of the input fields, it bubbles up to the form, calls handleChange
, and updates the state. Everything is the same with the exception that handleChange
is called only once.
1...
2<form action="" onChange={this.handleChange}>
3
4 <input type="text" id="name" placeholder="name" />
5 <input type="text" id="email" placeholder="email"/>
6 <input type="text" id="country" placeholder="country" />
7 <input type="text" id="city" placeholder="city" />
8
9 </form>
10...
Event bubbling can be used to greatly increase the performance gains of an app and drastically reduce the number of lines of code. However, in some cases, there could be a greater need to control the event from bubbling, and in situations where the form tag is not a parent of the input tag, this method doesn't work as intended. When uncertain, the best practice is to call the single onChange
event handler individually for every input and use the id captured inside the target property of the event object to identify different inputs.