An Introduction to Using Form Elements in React

React form components 101: What you need to know

Forms admittedly are not the most fun things to code in web development, but after exploring some of the tools offered in the React community, combined with a component based structure that comes naturally with React, I am beginning to relax somewhat when complex forms are required — it is easier to build, manage and validate form inputs with the range of tools React offers than say a PHP or Python solution. I know, that is a bold statement to make.

So in this article we will explore the following:

  • How to code bare-bones form elements in JSX within your React components
  • Use the onChange prop, as well as introducing defaultValue and defaultChecked props used to update the element status.
  • Placing each form element in it’s separate component that utilises PropTypes to document and check the props that are passed through to the component. At this stage we will also link the element to it’s state, making them fully functional.
  • Submitting a form and next steps, where I will briefly mention packages on validation and form management tools that are out the scope of this article. Once you have a solid foundation of form elements in React, moving onto validation tools (Yup recommended) and Form management (Formik recommended) will be a breeze — which will be the subject of another of my articles in the future.

There are indeed differences between coding HTML form elements vs JSX form elements. Let’s start exploring these differences. Think state and props!

Form Elements in React Need State

If you have visited forms before in React without using a form management package (that wrap a lot of functionality into their own components, which comes with advantages and disadvantages), you may have realised that some of the elements do not update when they seemingly should. the textarea, checkbox and radio are prime examples. What we need in these cases is to link state to these elements: when the state changes, the elements re-render with their updated status (checked, unchecked, selected, or simply a value or textares).

Note on using global state for form elements:

You may read on Stackoverflow that some developers advise linking form elements to your global Redux state. This is unnecessary. It is very likely that your form state will not be required outside of that form, therefore the additional overhead that storing a bunch of checkbox statuses in your Redux state will just add more complexity that you can avoid.
When would you use global state for forms? Well, there are not many scenarios. In the event that a form is interrupted by a different UI, you would want to save the form state so the user will not have to repeatedly fill the form again. But then again, requiring a user to return to a form after initiating the completion of it is a bad design choice.

Checkboxes, Radios and Textareas need state in order to update their values.

Coding the form elements bare-bones

Let’s check out what happens when we code some basic form elements inside a React component. At this stage we will not introduce any state to these elements. Below is a component that renders a simple form.

Copy and paste this into your React project to inspect the elements.

export class ExploreForms extends React.Component {  render() {
return(
<div><h2>Form elements</h2>
<form action="/myform" method="post">
<p>
Textbox: <br />
<input type="text" name="test" />
</p>
<p>Textarea: <br />
<textarea name="textarea_1" value="My Textarea" />
</p>
<p>Checkbox: <br />
<input type="checkbox" name="category" value="category1" /> Cat1
<input type="checkbox" name="category" value="category2" /> Cat2
<input type="checkbox" name="category" value="category3" /> Cat3
</p>
<p>Radio: <br />
<input type="radio" name="location" value="location1" /> Loc1
<input type="radio" name="location" value="location2" /> Loc2
<input type="radio" name="location" value="location3" /> Loc3
</p>
<p>Select: <br />
<select name="product" defaultValue={2}>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</select>
</p>
<input type="submit" value="Submit Form" />
</form>
</div>
);
}
}

That was quite a lot of non-functional markup! At this stage you can either test this code yourself, or take my word that the following behaviours occur.

Let’s quickly run through the behaviour of each element before moving on:

  • Textbox: The textbox appears to be working as expected. When we type with the textbox being active, the value updates, but nothing is committed to state to reflect the current value of the text field.
  • Textarea: We have run into our first issue. The textarea is not updating as we type. To see changes, this element requires state — we will explore how to tie it’s value to state in the next section, and also use a defaultValue prop to store the elements’ value.
  • Checkboxes: Another element that stubbornly will not check or uncheck itself! But not to worry, that is what React’s state is for. Not only do we need state for checkboxes, but we also use the defaultChecked prop that differs from what you are used to using in HTML.
  • Radios: Again, the radio list is not updating when I attempt to change the selected value. State is required here too, as well as using a defaultChecked prop that will reflect whether the element is selected or not.
  • Select dropdowns: Select dropdowns are working as expected, but like a text field, nothing is committed to state.

Although it would be required to link the textbox and select values to some state in order to work with them, we will not be doing so just yet.

Let’s fix up the textarea, checkbox and radio elements.

Introducing onChange, defaultValue, and defaultChecked props

Let’s focus on the textarea, checkbox and radio components, as these seem to be the most severely disabled in the React universe.

In order to get these elements to be responsive, we need to utilise the onChange prop to update the element state as we interact with it. In order to do this, it makes sense to create separate components for each form element which you can use throughout your projects as exportable components.

JSX also uses defaultValue and defaultChecked props to handle the values of these components. The following gist wraps our 3 form elements into their respective components, with some additional React intelligence with PropTypes thrown in:

Let’s run through some of of this code that is constructing our form element components.

Textarea

render () {     
return (
<textarea id={this.props.id}
name={this.props.name}
defaultValue={this.state.value}
onChange={this.handleChange} />
);
}

Within the textarea component, we use defaultValue which in turn displays this.state.value — the only variable our state contains. We also utilise onChange with handleChange function to update the state on every onChange event.

This is JSX and not HTML, so we do not wrap the textarea value within the textarea tag — it is self closing. Instead we use defaultValue.

Radio

...handleChange = () => {
this.setState({selected: !this.state.selected})
}
render () {
return (
<span>
<input
type="radio"
defaultChecked={this.state.selected}
onChange={this.handleChange}
id={this.props.id}
name={this.props.name}
/>
{this.props.label}
</span>
);
}

We adopt the same onChange prop and handleChange function with radio elements, but this time use the defaultChecked prop for the value. And as you can see, defaultChecked requires a boolean, which can either be true or false. Therefore when we are toggling the state, we can simply pass negation of the current value, with !this.state.selected.

Note: We do not need to store external state to sync an entire radio list! The list will automatically update as we switch to a new value. We may however want to pass the value to the parent component which will most likely be managing your form validation.

Checkbox

...handleChange = () => {
this.setState({checked: !this.state.checked})
}
render () {
return (
<span>
<input
type="checkbox"
defaultChecked={this.state.checked}
onChange={this.handleChange}
id={this.props.id}
name={this.props.name}
/>
{this.props.label}
</span>
);
}

Checkboxes work strikingly similar to the radio component. Nothing new here!

Working with PropTypes and form elements

Forms are a good use case for PropTypes and defaultProps features in React. The warnings in your Javascript consoles will become increasingly valuable as your forms become more complex.

Before using PropTypes, remember to import them into your script. (prop-types is bundled with React so there is no need to install this separately):

import PropTypes from 'prop-types';

PropTypes are a good use case when working with form elements — they will give us warnings when the incorrect data type is passed through to the component in development mode. isRequired will also flag a warning if such a value is not supplied.

defaultProps are also useful for form elements, if we want an initial value to be configured, defaultProps is a great way to do this.

Submitting a Form

Let’s now visit submitting a form.

This is rather simple in React, no additional components needed here:

export class MyForm extends React.Component {

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

console.log('submitting form. We need to validate it!');
}
render() {
return(
<form action="/myform"
method="post"
onSubmit={this.handleSubmit}>
//components here <input type="submit" value="Submit Form" /> </form>
)
}
}

The key takeaway here is that our submit handler function prevents the form from redirecting to another URL: we prevent this default behaviour with e.preventDefault(), which gives us the opportunity to validate our form without any redirects or refreshes. The event being triggered by submitting our form is being passed to handleSubmit automatically: we do not need to pass an event argument at the onSubmit prop.

Where to go from here

What we have not discussed in this article is how to collate all the form values from our form and validate them.

We could just inject a handler function as a separate prop into our form components to pass the value to my parent state as it is updated. That is perfectly valid.

But there is a better way

However, this is where I recommend using packages that do a wonderful job at form management and validation. Diving into these tools warrant a separate article, but let’s summarise what to explore from here:

Visit the official React form documentation, at
https://reactjs.org/docs/forms.html
to get a comprehensive overview of how each element is implemented. This will be a breeze after solidifying your base understanding from this article.

Yup — probably the simplest way to validate forms in React:
https://www.npmjs.com/package/yup

Formik — my favoured approach to managing forms in React:
https://www.npmjs.com/package/formik

Before diving into these tools and resources, try wrapping the textbox and select form elements into their own components, and expand them in any way you wish that suites your project requirements.

Programmer and Author. Director @ JKRBInvestments.com. Creator of LearnChineseGrammar.com for iOS.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store