React Hook Form Tutorial - Selects, Radio Buttons
/ 4 min read
In this post we are going to learn:
- How to handle
<select>
s and<input type="radio" />
using React Hook Form. - How to validate choices in a safer way using Zod and TypeScript.
Form Requirements
We’re going to build a flight booking form that contains mandatory multiple choices:
- Flight Class
- Economy
- Business
- First Class
- Payment Method
- Credit Card
- Bank Transfer
- Paypal
- Bitcoin
Define the validation schema
We can use strings to store the user’s choices and will deal with the validation later.
Build the UI
We define the inputs and selects the standard way web developers use them, nothing fancy.
The only difference is we register the UI controls using {...register('fieldName')}
so React Hook Form can handle the inputs and validations.
Let’s see what error messages will show up if we submit the form directly without filling anything.
Refining error messages
Let’s break down what happened to each field and fix them separately.
Flight Class
Whenever we see an error message like Expected X, received Y, that means the type expected input type in the schema and the actual input doesn’t match.
In this particular case it happens because the user hasn’t picked a choice and that’s why the flight class is null.
In order to fix this, we tell Zod what error message we want when we receive an invalid type.
Payment Method
Once you choose a flight class and submit the form, the Payment Method field is not validated and treated as valid.
The reason is because the default option is an empty string.
We can fix this by validating that an empty string is not a valid option using string length validation.
There’s a gotcha
The above solution works but it also has a weakness. Typos can creep up into the app because any string is accepted.
To give you an example, let’s change one of the radio button values to something else.
Submit the form and it will happily accept the value.
If you have a logic that depends on the user choosing first-class
as their choice, then this change would break your app.
We can fix this by limiting the acceptable strings that the fields can receive.
Limiting possible inputs
Zod.union and Zod.literal works perfectly to handle this case.
What this means is the flightClass field can only accept one these strings: economy
, business
, or first-class
.
The only strings accepted in paymentMethod are credit-card
, bank-transfer
, paypal
or bitcoin
.
If the First Class option contains an invalid value, here’s what it’ll look like.
Refining union error messages
Let’s refine the error messages again.
Note: We define the error messages using errorMap and we define them inside the first z.literal of the union.
I know feels weird but that’s the only working solution I’ve found so far.
Now the error messages should be back to the user friendly version.
Conclusion
- Radio buttons and selects are simple to handle using React Hook Form’s
{...register('fieldName')}
. - Strings can work to store choices but it’s not the safest option.
- Zod’s union and literal works better to store choices, limiting possible options and preventing typos.
- When the default error message is Expected X, received Y, use
invalid_type_error
inside the field to customize it.
Here’s the sandbox for you to try yourself.