Mastering the art of forms in React

Kolby Sisk
Udacity Eng & Data
Published in
5 min readJan 18, 2022

--

I recently published an article about re-rendering in React where I used a simplified form example to show how using state for the value of a text input causes the component to re-render on every change. I was surprised by the number of comments I received suggesting uncontrolled inputs went against the “React” way of creating inputs.

Here are a few examples:

“We use useState because you should generally try to control inputs in react...”

“…Going from controlled to uncontrolled components just to avoid rerendering seems a bit wonky and not react like to me...”

It made me realize there is confusion about forms in React, and honestly, a lot of React devs are making them much harder than they need to be.

Creating a simple form

🚧 Demo + Code

HTML5 added much needed functionality to forms. From new types like email, to regular expression pattern matching. Using the native features are advantageous because you get additional functionality like accessibility support. For example, when using the required attribute a screen reader will inform the user that the input is required.

Here are some common features that are possible without the burden of controlling input state:

  • Perform actions when the form changes
  • Receive form values on change/submit
  • Pattern validation
  • Custom error messages
  • Clear form after submit

The code above will validate the email, ensure both email and password have a value, then if validation passes it will call the handler which processes the data and clears the form.

💡 Don’t use controlled inputs until you need to.

Creating a slightly more complex form

🚧 Demo + Code

There are cases where you may need some state. Maybe you want to show error messages on blur, or conditionally show an input when another input is populated. You might think making the input controlled is the easiest/only solution, but there are better solutions.

We can use any of the input events to hook into the form. The above code uses the change event to determine if the url input should be visible or not, along with the blur event to check password validation when the user leaves the input. Using this method we could even add custom error messages using the checkValidity method.

💡 There are a number of useful input methods available — knowing what’s available can help you make better decisions.

Creating a form with React Hook Form

🚧 Demo + Code

At some point you’ll find that our simple solution just doesn’t scale well. If you’re rolling your own error messages and your form becomes larger than a few inputs — you’ll want to find a more scalable solution.

At this point, I turn to React Hook Form. I prefer RHF over other solutions because they prioritize DX and performance. Just like the forms we’ve created thus far — a RHF form will not cause a re-render on every change.

Utilizing the hooks provided by RHF gives us much more control over our form. It removes the need for us to manage our own error state, and does a lot of the form handling for us.

💡 Check out React Hook Forms docs and see just how much functionality they provide.

Better errors with Zod

🚧 Demo + Code

Zod is a schema validation tool that I highly recommend. It has great TypeScript support, and most importantly for our form endeavors — Zod integrates beautifully with RHF.

Not a whole lot changed, and that’s the point. We added a Zod schema which allows us to add advanced validations, we integrated it with RHF by using the resolver, and we changed the rendered error messages to be dynamic instead of static. Now when there is an input error our messages will be more specific about the problem, and that is completely powered by Zod. If we want, we can even customize the error messages.

💡 Learn more about schema validation and Zod.

Accessibility

When developing forms you’ll need ensure the form is accessible for all users. Depending on the inputs and functionality of your form you’ll likely need more than what is described here, but these are the most common accessibility concerns when working with forms.

Labeling

Every input needs a label. Setting a placeholder attribute isn’t enough. Additionally, every label needs to be associated with the input it describes. To achieve this we can use the for attribute in combination with the id attribute.

<label for="email">Enter your email</label>
<input id="email" type="email"/>

Invalidating & Alerting

If you’re rolling your own error messages then it’s important to alert the user when an error has occurred, and where. We can do this using aria roles alert and invalid.

<input aria-invalid={passwordErrorVisible ? "true" : "false"} name="password" type="password" minLength="6" onBlur={handlePasswordBlur} required />{passwordErrorVisible && <span role="alert">Password must be at least 6 characters</span>}

In the code above the handlePasswordBlur will call the checkValidity method and toggle passwordErrorVisible if there is an error. Since our error message has role="alert" the user will be informed when it is rendered on the screen, and by setting aria-invalid="true" we let the user know which input is invalid.

Describing

If you have an action on your form that is unusual, you should assist your users by describing the action. In order to make the description accessible we can use the describedby aria attribute.

<button aria-describedby="description">Revert</button><div id="description">Reverting will undo any changes that have been made
since the last save.</div>

💡 MDN has great documentation on improving accessibility for forms.

Conclusion

I hope this is helpful to show that you don’t need to immediately reach for controlled inputs. Most of what you need can be accomplished without them, and the performance cost that comes with them. If you have a more complex form then I recommend pulling in RHF + Zod for the performance and DX that comes with them.

Udacity is currently looking for skilled React developers to help us build talent transformation tools. If you’re skilled in developing international UIs at scale then reach out to me on Twitter @kolbysisk.

--

--

Builder of software with a passion for learning. I specialize in web development and user experience design. www.kolbysisk.com