Photo by Scott Webb on Unsplash

Forms and Semantic HTML in React

--

Recently I was debugging some React code that was allowing a form to submit its contents even when the user did not check a required checkbox. Client side, HTML5 validations weren’t running at all, and the user had to wait for server errors to know that they had typed invalid input.

HTML5 validations should just work, right? If a field in a form is required, we should see the warning message pop up if the field is blank.

As it turns out, HTML5 validations won’t run if your form isn’t semantically correct. The developers on my team all knew the components of an HTML form — you need a form element and a button of type submit. Cool. But still no dice.

Understanding what was going on was made more complicated by the fact that we had a complex DOM tree and were using a shared modal component that was designed to be flexible for different uses. When rendering the modal, you could pass in children as contents, and also a prop indicating whether or not the children would pass in the <form /> element or let the modal component render the <form /> .

After taking a closer look at our DOM tree, we found that our HTML5 validations weren’t running because, while we had all the right pieces of a form, we weren’t being semantic.

Semantic Forms

In plain HTML, this is all you need:

<form>
<button type="submit"></button>
</form>
  • The button of type submit is the child of your <form />element.
  • In plain HTML, the <form /> has an implicit onSubmit event.
  • A button click triggers an event that bubbles up to the form and triggers the form’s onSubmit event.

In React it looks like this:

HTML5 validations will run before the form’s onSubmit function is called, so you can still preventDefault in your handleSubmit function. If the form isn’t valid, i.e. the user hasn’t checked the checkbox, your handleSubmit function won’t be called. There’s no need to write additional code to ensure this is the case.

Non-Semantic “Forms”

There are a couple of ways to render a form that seems legit but is actually a fake form.

  1. The submit button is a sibling, not a child, of the form element.
<form>
... form contents
</form>
<button type="submit"></button>
  • When the submit button is not a child of the form, the event of clicking on the button will not bubble up to the form, and itsonSubmit event will never fire.
  • No HTML5 validations :(

In this case, you can still fire your handleSubmit function from the button, if you’re passing all the data you need for submission there. But you’re not leveraging real HTML form functionality. In fact, the <form> element might as well be a <div> — you’re just leveraging React to pass data and fire events on arbitrary DOM elements.

2. The submit button is a child of the form but has its own onClick

Even if your HTML is correct, you can still highjack the semantic form event flow by giving your button an onClick prop that calls your handleSubmit function.

When this is the case, you prevent the event from bubbling up to the form level. Essentially, no submit event ever occurs. Again, if you don’t call the form’s onSubmit , you can’t leverage HTML5 validations.

Takeaways

Prioritizing semantic HTML in React will lead to advantages in the short and long run. You get things for free, like validations and autofill. It also makes a great deal of difference for accessibility, since screen readers know how to parse semantic HTML but don’t know how to interpret arbitrary markup. Finally, it will allow you to cut down on boilerplate and help guide component design.

It’s not always obvious to “see” when your HTML is actually semantic, especially if you are leveraging flexible, shared components or have rendered markup that varies based on a number of conditions. But taking the time to create a real form amidst all the complexity is worth it.

--

--