React Forms: Using Refs

rfp-robotRFP ROBOT: Website Request for Proposal Generator

The time has come for a new website (or website redesign), which means you need to write a website request for proposal or web RFP. A Google search produces a few examples, but they vary wildly and don’t seem to speak really to your goals for developing or redesigning a new website. You need to write a website RFP that will clearly articulate your needs and generate responses from the best website designers and developers out there. But how?

Have no fear, RFP Robot is here. He will walk you through a step-by-step process to help you work through the details of your project and create a PDF formatted website design RFP that will provide the information vendors need to write an accurate bid. RFP Robot will tell you what info you should include, point out pitfalls, and give examples.


React provides two standard ways to grab values from <form> elements. The first method is to implement what are called controlled components (see my blog post on the topic) and the second is to use React’s ref property.
Controlled components are heavy duty. The defining characteristic of a controlled component is the displayed value is bound to component state. To update the value, you execute a function attached to the onChange event handler on the form element. The onChange function updates the state property, which in turn updates the form element’s value.
(Before we get too far, if you just want to see the code samples for this article: here you go!)

Here’s an example of a controlled component:
import React, { Component } from ‘react’;

class ControlledCompExample extends Component {
constructor() {
super();
this.state = {
fullName: ”
}
}
handleFullNameChange = (e) => {
this.setState({
fullName: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
console.log(this.state.fullName)
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label htmlFor=”fullName”>Full Name</label>
<input
type=”text”
value={this.state.fullName}
onChange={this.handleFullNameChange}
name=”fullName” />
<input type=”submit” value=”Submit” />
</form>
</div>
);
}
}

export default ControlledCompExample;
The value of the input is this.state.fullName (lines 7 and 26). The onChange function is handleFullNameChange (lines 10 – 14, and line 27).
The main advantages of controlled components are:

You are set up to easily validate user input.
You can dynamically render other components based on the value of the controlled component. For example, the value a user selects from a dropdown (e.g. ‘dog’ or ‘cat’) can control which other form components (e.g. a checkbox set of breeds) are rendered in the form.

The downside to controlled components is the amount of code you have to write. You need a state property to pass to the form element as props, and you need a function to update the value of this property.
For one form element this isn’t an issue – but if you have a large, complex form (that doesn’t need dynamic rendering or real-time validation), you’ll find yourself writing a ton of code if you overuse controlled components.
An easier and less labor-intensive way to grab values from a form element is to use the ref property. Different form elements and component compositions require different strategies, so the rest of this post is divided into the following sections.

Text inputs, number inputs, and selects
Passing props from child to parent
Radio sets
Checkbox sets

1. Text inputs, number inputs, and selects
Text and number inputs provide the most straightforward example of using refs. In the ref attribute of the input, add an arrow function that takes the input as an argument. I tend to name the argument the same as the element itself as seen on line 3 below:
<input
type=”text”
ref={input => this.fullName = input} />
Since it’s an alias for the input element itself, you can name the argument whatever you’d like:
<input
type=”number”
ref={cashMoney => this.amount = cashMoney} />
You then take the argument and assign it to a property attached to the class’s this keyword. The inputs (i.e. the DOM node) are now accessible as this.fullName and this.amount. The values of the inputs are accessible as this.fullName.value and this.amount.value.
The same strategy works for select elements (i.e. dropdowns).
<select
ref={select => this.petType = select}
name=”petType”>
<option value=”cat”>Cat</option>
<option value=”dog”>Dog</option>
<option value=”ferret”>Ferret</option>
</select>
The value selected is accessible as this.petType.value.
2. Passing props from child to parent
With a controlled component, getting the value from a child component to a parent is straightforward – the value already lives in the parent! It’s passed down to the child. An onChange function is also passed down and updates the value as the user interacts with the UI.
You can see this at work in the controlled component examples in my previous post.
While the value already lives in the parent’s state in controlled components, this is not so when using refs. With refs, the value resides in the DOM node itself, and must be communicated up to the parent.
To pass this value from child to parent, the parent needs to pass down a ‘hook’, if you will, to the child. The child then attaches a node to the ‘hook’ so the parent has access to it.
Let’s look at some code before discussing this further.
import React, { Component } from ‘react’;

class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();
console.log(‘first name:’, this.firstName.value);
this.firstName.value = ‘Got ya!’;
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<CustomInput
label={‘Name’}
firstName={input => this.firstName = input} />
<input type=”submit” value=”Submit” />
</form>
</div>
);
}
}

function CustomInput(props) {
return (
<div>
<label>{props.label}:</label>
<input type=”text” ref={props.firstName}/>
</div>
);
}

export default RefsForm;
Above you see a form component RefForm, and an input component called CustomInput. Usually, the arrow function is on the input itself, but here it’s being passed down as a prop (see lines 15 and 27). Since the arrow function resides in the parent, the this of this.firstName lives in the parent.
The value of the child input is being assigned to the this.firstName property of the parent, so the child’s value is available to the parent. Now, in the parent, this.firstName refers to a DOM node in the child component (i.e. the input in CustomInput).
Not only can the DOM node of the input be accessed by the parent, but the value of the node can also be assigned from within the parent. This is demonstrated on line 7 above. Once the form is submitted, the value of the input is set to ‘Got ya!’.
This pattern is a bit mind bending, so stare at it for a while and play around with the code until it sinks in.
You may be better off making radios and checkboxes controlled components, but if you really want to use refs the next two sections are for you.
3. Radio sets
Unlike text and number input elements, radios come in sets. Each element in a set has the same name attribute, like so:
<form>
<label>
Cat
<input type=”radio” value=”cat” name=”pet” />
</label>
<label>
Dog
<input type=”radio” value=”dog” name=”pet” />
</label>
<label>
Ferret
<input type=”radio” value=”ferret” name=”pet” />
</label>
<input type=”submit” value=”Submit” />
</form>
There are three options in the “pet” radio set – “cat”, “dog”, and “ferret”.
Since the whole set is the object of our concern, setting a ref on each radio input is not ideal. And, unfortunately, there’s no DOM node that encapsulates a set of radios.
Retrieving the value of the radio set can be obtained through three steps:

Set a ref on the <form> tag (line 20 below).
Extract the set of radios from the form. In this case, it is the pet set (line 9 below).

A node list and a value is returned here. In this case, this node list includes three input nodes, and the value selected.
Keep in mind that a node list looks like an array but is not, and lacks array methods. There’s more on this topic in the next section.

Grab the value of the set using dot notation (line 13 below).

import React, { Component } from ‘react’;

class RefsForm extends Component {

handleSubmit = (e) => {
e.preventDefault();

// extract the node list from the form
// it looks like an array, but lacks array methods
const { pet } = this.form;

// a set of radios has value property
// checkout out the log for proof
console.log(pet, pet.value);
}

render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<label>
Cat
<input type=”radio” value=”cat” name=”pet” />
</label>
<label>
Dog
<input type=”radio” value=”dog” name=”pet” />
</label>
<label>
Ferret
<input type=”radio” value=”ferret” name=”pet” />
</label>
<input type=”submit” value=”Submit” />
</form>
</div>
);
}
}

export default RefsForm;
This works even if you are composing a form from children components. Although there’s more logic in the components, the technique for getting the value from the radio set remains the same.
import React, { Component } from ‘react’;

class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();

// extract the node list from the form
// it looks like an array, but lacks array methods
const { pet } = this.form;

// a set of radios has value property
// checkout out the log for proof
console.log(pet, pet.value);
}

render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<RadioSet
setName={‘pet’}
setOptions={[‘cat’, ‘dog’, ‘ferret’]} />
<input type=”submit” value=”Submit” />
</form>
</div>
);
}
}

function RadioSet(props) {
return (
<div>
{props.setOptions.map(option => {
return (
<label
key={option}
style={{textTransform: ‘capitalize’}}>
{option}
<input
type=”radio”
value={option}
name={props.setName} />
</label>
)
})}
</div>
);
}

export default RefsForm;
4. Checkbox sets
Unlike a radio set, a checkbox set may have multiple values selected. This makes extracting these values a little more complicated than extracting the value of a radio set.
Retrieving the selected values of the checkbox set can be done through these five steps:

Set a ref on the <form> tag (line 27 below).
Extract the set of checkboxes from the form. In this case, it is the pet set (line 9).

A node list and a value is returned here.
Keep in mind that a node list looks like an array but is not, and lacks array methods, which takes us to the next step…

Convert the node list to an array, so array methods are available (checkboxArray on line 12).
Use Array.filter() to grab only the checked checkboxes (checkedCheckboxes on line 15).
Use Array.map() to keep only the values of the checked checkboxes (checkedCheckboxesValues on line 19).

import React, { Component } from ‘react’;

class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();

// extract the node list from the form
// it looks like an array, but lacks array methods
const { pet } = this.form;

// convert node list to an array
const checkboxArray = Array.prototype.slice.call(pet);

// extract only the checked checkboxes
const checkedCheckboxes = checkboxArray.filter(input => input.checked);
console.log(‘checked array:’, checkedCheckboxes);

// use .map() to extract the value from each checked checkbox
const checkedCheckboxesValues = checkedCheckboxes.map(input => input.value);
console.log(‘checked array values:’, checkedCheckboxesValues);
}

render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<label>
Cat
<input type=”checkbox” value=”cat” name=”pet” />
</label>
<label>
Dog
<input type=”checkbox” value=”dog” name=”pet” />
</label>
<label>
Ferret
<input type=”checkbox” value=”ferret” name=”pet” />
</label>
<input type=”submit” value=”Submit” />
</form>
</div>
);
}
}

export default RefsForm;
Using a checkbox set child component works just like the radio set example in the previous section.
import React, { Component } from ‘react’;

class RefsForm extends Component {
handleSubmit = (e) => {
e.preventDefault();

// extract the node list from the form
// it looks like an array, but lacks array methods
const { pet } = this.form;

// convert node list to an array
const checkboxArray = Array.prototype.slice.call(pet);

// extract only the checked checkboxes
const checkedCheckboxes = checkboxArray.filter(input => input.checked);
console.log(‘checked array:’, checkedCheckboxes);

// use .map() to extract the value from each checked checkbox
const checkedCheckboxesValues = checkedCheckboxes.map(input => input.value);
console.log(‘checked array values:’, checkedCheckboxesValues);
}

render() {
return (
<div>
<form
onSubmit={this.handleSubmit}
ref={form => this.form = form}>
<CheckboxSet
setName={‘pet’}
setOptions={[‘cat’, ‘dog’, ‘ferret’]} />
<input type=”submit” value=”Submit” />
</form>
</div>
);
}
}

function CheckboxSet(props) {
return (
<div>
{props.setOptions.map(option => {
return (
<label
key={option}
style={{textTransform: ‘capitalize’}}>
{option}
<input
type=”checkbox”
value={option}
name={props.setName} />
</label>
)
})}
</div>
);
}

export default RefsForm;
Conclusion
If you don’t need to:

monitor the value of a form element in real-time (e.g. in order to render subsequent components based on user input), or
perform custom validation in real-time,

then using refs to grab data from form elements is a good bet.
The primary value of using refs over controlled component is that, in most cases, you will write less code. The exceptional case is that of checkbox sets (and radios to a lesser degree). For checkbox sets, the amount of code you save by using refs is minimal, so it’s less clear whether to use a controlled component or refs.

React Forms: Using Refs is a post from CSS-Tricks
Source: CssTricks

Posted on May 23, 2017 in Drupal Development

Share the Story

Back to Top