Forms

Customizing with React-Hook-Form

When working with our Form component, there are three areas to be aware of:

  1. The Form Components (eg. Form, FormField, FormFooter, etc).

  2. Our custom Inputs (found in the Input folder).

  3. Our custom hooks (useForm and useFormValidation).

The main form lives in, you guessed it, Form.js. You'll notice right away that we use react-hook-form, a powerful tool for creating performant and flexible forms in React. If you have never used react-hook-form (hereafter referred to as RHF) before, please take a moment to familiarize yourself with it before you continue reading.

There are three separate sections that occur in this file:

  1. We grab all the methods and values from our useForm hook - we choose to keep these helper functions and RHF methods separate for cleanliness.

  2. We map through each Input field that is included in Sanity and return an array of the corresponding Input component (InputCheckbox, InputRadio, etc).

  3. We render the Form, wrapped in the RHF FormProvider.

A simple Text component that renders when formHasSubmitted is true.

A component that contains both the Label, Input and any errors related to the specific Input.

This typically contains the FormHCaptcha, FormSubmitButton and any error messages related to the Form.

Are you a robot?

Because when no form data sent, you're gonna have a bad day.

We leverage two different libraries for our input components - ReactSelect and Radix UI. Because these are third party controlled components, we need to make use of RHF's useController hook and Controller component to ensure they behave as expected within the context of RHF.

You can think of this component as the top of the funnel. This is the generic component used within Form.js that handles all the basic styling and passes on the expected props. How does React know which type of Input to render? With this nifty one-liner in Form.js:

const Input = getInput(type);

Our drop-down select input. ReactSelect has its own way of implementing styles, so we have separate files to handle those.

Our styled checkbox and radio primitives, supplied by Radix UI.

Note: the parseChoices function that we extract from useFormContext fails if the choices aren't formatted a specific way in Sanity. The format should always be label:value (separated by a colon), and each choice should be on it's own line.

A component that handles the communication of any input error or validation success. At its most basic, it includes an icon with a subtle animation (InputErrors inlcudes the error messaged passed down from RHF).

The Controller is still used in this case, due to our use of an extra FormField wrapper.

An additional style wrapper with variants for radio and checkboxes.

Finally we come to our two custom hooks. They allow us to keep certain form methods and values in a contained place for code cleanliness and ease of reference.

Regex is nasty to write and nasty to look at. Here is where we store our validation for email and telephone fields as well as a default error message for any required fields.

This hook houses the majority of our RHF functionality, along with loading states some utility functions used in the various Input files. Of note is the getInput function that was mentioned in the Input.js section above. We use the type prop to determine what Input component we want to render, and leverage Next's dynamic imports to ensure we're only including the components we need.

💡
See something that is out of date or that could be improved?Please let the team know!1. You can create a Github issue2. Pull down the repo and create a PR with your suggested changes implimented.3. Or just let someone know in the R&P Slack Channel.We love making things better.