前一篇文章示範了如何使用 Form 表單送出資料,不過這個表單並沒有做任何的驗證處理,例如:使用者沒有輸入資料、輸入的資料格式不正確等等,這些都是需要處理的,這一篇文章就來示範如何使用 React Hook Form 來做表單驗證。
React Hook Form 是一個 React 的表單驗證套件,它的特色是使用 Hook 來處理表單驗證,在使用上非常簡單,只需要使用 useForm
Hook 就可以了,它的 API 也非常簡單,只需要使用 register
、handleSubmit
、errors
、watch
這幾個 API 就可以完成表單驗證,而且基本上就可以少掉很多的 useState,因為它會自動幫你處理表單的資料。
首先先安裝 React Hook Form:
$ npm install react-hook-form
首先在 page.tsx
中引入:
import { useForm, SubmitHandler } from 'react-hook-form'
並且使用 useForm
Hook:
export type FormInputs = {
text: string
qrType: string
qrSize: number
qrColor: string
qrBgColor: string
}
export default function Home() {
const { register, handleSubmit, setValue } = useForm<FormInputs>({
defaultValues: {
qrSize: 500,
qrColor: '#000000',
qrBgColor: '#ffffff'
}
})
// 省略
}
這裡使用了 FormInputs
來定義表單的資料型別,並且使用 useForm
Hook 來處理表單,useForm
Hook 會回傳一個物件,裡面包含了 register
、handleSubmit
、setValue
等等的 API,這些 API 等一下會使用到。
接下來把表單中的每一個欄位都接上 register
API:
<form className='flex flex-col gap-y-5'>
<SelectType register={register} />
<TextInput register={register} />
<div className='flex items-center justify-evenly'>
<SizeSlider register={register} setValue={setValue} />
<ColorPicker
register={register}
label='選擇顏色'
name='qrColor'
setValue={setValue}
/>
<ColorPicker
register={register}
label='選擇背景顏色'
name='qrBgColor'
setValue={setValue}
/>
</div>
<div className='flex justify-center mt-5'>
<button
type='submit'
className='bg-green-500 hover:bg-green-100 text-white hover:text-slate-700 font-bold py-2 px-4 rounded text-center'
>
產生 QR Code
</button>
</div>
</form>
有些欄位需要使用 setValue
API 來設定預設值,例如:SizeSlider
、ColorPicker
,這些欄位都需要使用 setValue
API 來設定預設值。
import { FC } from 'react'
import { UseFormRegister } from 'react-hook-form'
import { FormInputs } from '../page'
interface SelectTypeProps {
register: UseFormRegister<FormInputs>
}
const SelectType: FC<SelectTypeProps> = ({ register }) => (
<div className='mb-4'>
<label className='block font-bold mb-2'>選擇 QR Code 類型</label>
<select
className='w-full p-2 border rounded focus:outline-none focus:ring focus:border-blue-300'
{...register('qrType')}
>
<option value='URL'>URL</option>
<option value='電話'>電話</option>
<option value='地址'>地址</option>
<option value='Email'>Email</option>
</select>
</div>
)
export default SelectType
這裡使用了 UseFormRegister
來定義 register
的型別,並且使用 register
API 來設定欄位的名稱,這裡的名稱就是 FormInputs
中的屬性名稱,例如:qrType
、qrSize
、qrColor
、qrBgColor
。
import { FC } from 'react'
import { UseFormRegister } from 'react-hook-form'
import { FormInputs } from '../page'
interface TextInputProps {
register: UseFormRegister<FormInputs>
}
const TextInput: FC<TextInputProps> = ({ register }) => (
<div className='mb-4'>
<label className='block font-bold mb-2'>輸入文字</label>
<input
type='text'
className='w-full p-2 border rounded focus:outline-none focus:ring focus:border-blue-300'
{...register('text', { required: true })}
/>
</div>
)
export default TextInput
跟 SelectType
蠻類似的,只是這裡多了一個 required
來設定必填。
import { FC } from 'react'
import { UseFormRegister, UseFormSetValue } from 'react-hook-form'
import { FormInputs } from '../page'
interface SizeSliderProps {
register: UseFormRegister<FormInputs>
setValue: UseFormSetValue<FormInputs>
}
const SizeSlider: FC<SizeSliderProps> = ({ register, setValue }) => (
<div className='mb-4'>
<label className='block font-bold mb-2'>調整圖片大小</label>
<input
type='range'
min='100'
max='2000'
{...register('qrSize')}
onChange={(e) => {
setValue('qrSize', parseInt(e.target.value, 10))
}}
/>
</div>
)
export default SizeSlider
這裡使用了 UseFormSetValue
來定義 setValue
的型別,並且使用 setValue
API 來設定預設值。
import { FC } from 'react'
import { UseFormRegister, UseFormSetValue } from 'react-hook-form'
import { FormInputs } from '../page'
interface ColorPickerProps {
label: string
name: keyof FormInputs
register: UseFormRegister<FormInputs>
setValue: UseFormSetValue<FormInputs>
}
const ColorPicker: FC<ColorPickerProps> = ({
label,
name,
register,
setValue
}) => {
return (
<div className='mb-4'>
<label className='block font-bold mb-2'>{label}</label>
<input
type='color'
{...register(name)}
onChange={(e) => {
setValue(name, e.target.value)
}}
/>
</div>
)
}
export default ColorPicker
因為 ColorPicker
會被使用在兩個地方,所以這裡使用了 keyof FormInputs
來定義 name
的型別,並且使用 setValue
API 來設定預設值。
最後就是處理表單送出的部分,這裡使用了 handleSubmit
API 來處理表單送出:
const fetchQrcodeSvg = async (formData: FormInputs) => {
try {
const typeMapping: { [key in FormInputs['qrType']]: keyof QrCodeData } = {
URL: 'url',
電話: 'phone',
地址: 'address',
Email: 'email'
}
const dataKey = typeMapping[formData.qrType]
const data: QrCodeData = {
[dataKey]: formData.text,
foreground: formData.qrColor,
background: formData.qrBgColor,
dimensions: formData.qrSize
}
const response = await generateQrcode.getSvg(data)
const blob = new Blob([response.data], { type: 'image/svg+xml' })
const objectURL = URL.createObjectURL(blob)
setImgSrc(objectURL)
} catch (_) {
console.error('Error fetching image:')
}
}
const onSubmit: SubmitHandler<FormInputs> = async (data) => {
await fetchQrcodeSvg(data)
}
<form onSubmit={handleSubmit(onSubmit)} className='flex flex-col gap-y-5'>
// 省略
<div className='flex justify-center mt-5'>
<button
type='submit'
className='bg-green-500 hover:bg-green-100 text-white hover:text-slate-700 font-bold py-2 px-4 rounded text-center'
>
產生 QR Code
</button>
</div>
</form>
現在當使用者沒有輸入文字時,就沒辦法送出表單產生 QR Code 了。
以上就是使用 React Hook Form 來處理表單驗證的方法,這樣就可以少掉很多的 useState,而且使用起來也非常簡單。
不過我們還可以再進一步,例如:當使用者輸入不符合類別的文字時,就顯示錯誤訊息,這個部分就下一篇文章再處理,我們明天見👋!