iT邦幫忙

2023 iThome 鐵人賽

DAY 18
0
SideProject30

30 天用 Rust 打造 QR Code 製造機系列 第 18

Day 18 - React Hook Form 做好你的表單驗證

  • 分享至 

  • xImage
  •  

前一篇文章示範了如何使用 Form 表單送出資料,不過這個表單並沒有做任何的驗證處理,例如:使用者沒有輸入資料、輸入的資料格式不正確等等,這些都是需要處理的,這一篇文章就來示範如何使用 React Hook Form 來做表單驗證。

React Hook Form

React Hook Form 是一個 React 的表單驗證套件,它的特色是使用 Hook 來處理表單驗證,在使用上非常簡單,只需要使用 useForm Hook 就可以了,它的 API 也非常簡單,只需要使用 registerhandleSubmiterrorswatch 這幾個 API 就可以完成表單驗證,而且基本上就可以少掉很多的 useState,因為它會自動幫你處理表單的資料。

安裝套件

首先先安裝 React Hook Form:

$ npm install react-hook-form

使用 useForm

首先在 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 會回傳一個物件,裡面包含了 registerhandleSubmitsetValue 等等的 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 來設定預設值,例如:SizeSliderColorPicker,這些欄位都需要使用 setValue API 來設定預設值。

設定各個 Component

SelectType

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 中的屬性名稱,例如:qrTypeqrSizeqrColorqrBgColor

TextInput

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 來設定必填。

SizeSlider

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 來設定預設值。

ColorPicker

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,而且使用起來也非常簡單。

不過我們還可以再進一步,例如:當使用者輸入不符合類別的文字時,就顯示錯誤訊息,這個部分就下一篇文章再處理,我們明天見👋!


上一篇
Day 17 - 使用 Form 表單送出資料
下一篇
Day 19 - 驗證輸入的文字是否正確
系列文
30 天用 Rust 打造 QR Code 製造機30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言