Customizing with React-Hook-Form
When working with our Form component, there are three areas to be aware of:
The Form Components (eg. Form
, FormField
, FormFooter
, etc).
Our custom Inputs (found in the Input
folder).
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:
We grab all the methods and values from our useForm
hook - we choose to keep these helper functions and RHF methods separate for cleanliness.
We map through each Input field that is included in Sanity and return an array of the corresponding Input
component (InputCheckbox
, InputRadio
, etc).
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.