Working with Forms in React

Almost every application needs to accept user input at some point, and this is usually achieved with the venerable HTML form and its collection of input controls. If you’ve recently started learning React, you’ve probably arrived at the point where you’re now thinking, “So how do I work with forms?”

This article will walk you through the basics of using forms in React to allow users to add or edit information. We’ll look at two different ways of working with input controls and the pros and cons of each. We’ll also take a look at how to handle validation, and some third-party libraries for more advanced use cases.

Uncontrolled Inputs

The most basic way of working with forms in React is to use what are referred to as “uncontrolled” form inputs. What this means is that React doesn’t track the input’s state. HTML input elements naturally keep track of their own state as part of the DOM, and so when the form is submitted we have to read the values from the DOM elements themselves.

In order to do this, React allows us to create a “ref” (reference) to associate with an element, giving access to the underlying DOM node. Let’s see how to do this:

class SimpleForm extends React.Component { constructor(props) { super(props); // create a ref to store the DOM element this.nameEl = React.createRef(); this.handleSubmit = this.handleSubmit.bind(this); } handleSubmit(e) { e.preventDefault(); alert(this.nameEl.current.value); } render() { return ( <form onSubmit={this.handleSubmit}> <label>Name: <input type="text" ref={this.nameEl} /> </label> <input type="submit" name="Submit" /> </form> ) } } 

As you can see above, for a class-based component you initialize a new ref in the constructor by calling React.createRef, assigning it to an instance property so it’s available for the lifetime of the component.

In order to associate the ref with an input, it’s passed to the element as the special ref attribute. Once this is done, the input’s underlying DOM node can be accessed via this.nameEl.current.

Let’s see how this looks in a functional component:

function SimpleForm(props) { const nameEl = React.useRef(null); const handleSubmit = e => { e.preventDefault(); alert(nameEl.current.value); }; return ( <form onSubmit={handleSubmit}> <label>Name: <input type="text" ref={nameEl} /> </label> <input type="submit" name="Submit" /> </form> ); } 

There’s not a lot of difference here, other than swapping out createRef for the useRef hook.

Example: login form

function LoginForm(props) { const nameEl = React.useRef(null); const passwordEl = React.useRef(null); const rememberMeEl = React.useRef(null); const handleSubmit = e => { e.preventDefault(); const data = { username: nameEl.current.value, password: passwordEl.current.value, rememberMe: rememberMeEl.current.checked, } // Submit form details to login endpoint etc. // ... }; return ( <form onSubmit={handleSubmit}> <input type="text" placeholder="username" ref={nameEl} /> <input type="password" placeholder="password" ref={passwordEl} /> <label> <input type="checkbox" ref={rememberMeEl} /> Remember me </label> <button type="submit" className="myButton">Login</button> </form> ); } 

View on CodePen

While uncontrolled inputs work fine for quick and simple forms, they do have some drawbacks. As you might have noticed from the code above, we have to read the value from the input element whenever we want it. This means we can’t provide instant validation on the field as the user types, nor can we do things like enforce a custom input format, conditionally show or hide form elements, or disable/enable the submit button.

Fortunately, there’s a more sophisticated way to handle inputs in React.

Controlled Inputs

An input is said to be “controlled” when React is responsible for maintaining and setting its state. The state is kept in sync with the input’s value, meaning that changing the input will update the state, and updating the state will change the input.

Let’s see what that looks like with an example:

class ControlledInput extends React.Component { constructor(props) { super(props); this.state = { name: '' }; this.handleInput = this.handleInput.bind(this); } handleInput(event) { this.setState({ name: event.target.value }); } render() { return ( <input type="text" value={this.state.name} onChange={this.handleInput} /> ); } } 

As you can see, we set up a kind of circular data flow: state to input value, on change event to state, and back again. This loop allows us a lot of control over the input, as we can react to changes to the value on the fly. Because of this, controlled inputs don’t suffer from the limitations of uncontrolled ones, opening up the follow possibilities:

  • instant input validation: we can give the user instant feedback without having to wait for them to submit the form (e.g. if their password is not complex enough)
  • instant input formatting: we can add proper separators to currency inputs, or grouping to phone numbers on the fly
  • conditionally disable form submission: we can enable the submit button after certain criteria are met (e.g. the user consented to the terms and conditions)
  • dynamically generate new inputs: we can add additional inputs to a form based on the user’s previous input (e.g. adding details of additional people on a hotel booking)


As I mentioned above, the continuous update loop of controlled components makes it possible to perform continuous validation on inputs as the user types. A handler attached to an input’s onChange event will be fired on every keystroke, allowing you to instantly validate or format the value.

Continue reading Working with Forms in React on SitePoint.

Similar Posts