React hook form(1) - useForm & 它的回傳值(1)


Posted by TempuraEngineer on 2023-02-04

目錄


React hook form是什麼

這篇提到過由於用uncontrolled component來做表單滿的,所以官方推薦盡量使用controlled component

然而controlled component有2個問題

  1. 每次資料更新就會呼叫setState,這會導致一直re-render,而導致效能低落
  2. 如果表單有多個欄位就會有多個useState,或者需要透過useReducer來管理表單狀態,會變得相當繁瑣

React hook form就是為了解決這個問題而產生的表單套件,大致上是基於uncontrolled component建構成的

但是如果要用controlled component(ex: 使用MUI的input),只要用Controller這個組件就行了


useForm

React-hook-form一切的開端,register、unregister、resetField、setValue、getValues、handleSubmit、watch、formState...都是由它回傳

接收一個參數是option可選擇是否設置,不設的話甚麼都不用傳


defaultValues

預設值可以同步(如下)或非同步(ex: 撈資料的非同步含式)的方式設置

不要提供undefined給defaultValues,因為這會和controlled component的default state產生衝突

// 設置default value
useForm({
    defaultValues: {
      firstName: '',
      lastName: '',
      category: '',
      checkbox: [],
      radio: ''
    }
  });


resetOptions

resetOptions是用來設定reset被呼叫後,應該有什麼行為(ex: keepDirty、keepError)

這個option是reset方法的option的reference,所以會影響到reset後的行為

useForm({
  values,
  resetOptions: {
    keepDirtyValues: true, // user-interacted input will be retained
    keepErrors: true, // input errors will be retained with value update
  }
})


resolver

resolver用於驗證,常配合一些驗證的library使用

可以自己寫驗證邏輯,或者當驗證邏輯比較複雜時,可以配合library(ex: ajv)使用

另外還會需要安裝@hookform/resolvers

詳細可參考官方範例


register

register是React hook form的核心,用於將欄位的值註冊到提交出去的結果上,這樣欄位的能被送出、驗證

import {useForm} from 'react-hook-form';

import TextField from '@mui/material/TextField';
import Button from '@mui/material//Button';
import SearchIcon from '@mui/icons-material/Search';

interface PersonFilter {
    name: string;
    age: number;
    phone: string;
}

const Form = () => {
    const {register} = useForm<PersonFilter>();
    const { onChange, onBlur, name, ref } = register('name'); 

    return (
           {/* 以下相同於{...register('name')} */}
           <TextField
                label='姓名'
                variant='outlined'
                onChange={onChange} // 訂閱input change事件
                onBlur={onBlur} // 訂閱input blur事件
                name={name} // 欄位名
                ref={ref} // 用來註冊name的ref
            />

           <TextField
                label='年齡'
                variant='outlined'
                {...register('age')}
           />
    );
}

register支援巢狀結構,須注意但如果是陣列時仍然要用.來存取值

<TextField
   label='1號學生姓名'
   variant='outlined'
   {...register('student.0.name')}
/>


驗證

// 上略

// 欄位為必填,驗證是否有填寫,且長度不超過20
<TextField
   label='姓名'
   variant='outlined'
   {...register('name', {
       required: true,
       maxLength: 20
   })}
/>

// 最大值為120
<TextField
   label='年齡'
   variant='outlined'
    {...register('age', {
        max: 120
    })}
/>

// 用正規表達式驗證
// 驗證號碼是否開頭09、共10位數字
<TextField
   label='電話'
   variant='outlined'
    {...register('phone', {
        pattern: /^09[0-9]{8}$/
    })}
/>

// 自訂驗證規則(傳callback或物件)
// 驗證號碼是否長度到為10字
<TextField
   label='電話'
   variant='outlined'
    {...register('phone', {
        validate: (value, formValues) => value.length === 10
    })}
/>


handleSubmit

接收2個參數,分別為驗證成功時呼叫的callback,另一個則為驗證失敗時呼叫的callback,callback可為非同步函式

如果表單的內容驗證合法的,handleSubmit就能得到資料

另外由於handleSubmit處理callback內發生的錯誤,所以官方推薦自己加catch來處理錯誤

// 上略

// 使用apollo client的refetch來抓新的資料
const handleValidateSuccess = useCallback(
    ({ name, age }) => { // 驗證成功後可以收到表單資料
        try{
            void refetch({
                filter: {
                  name: name || undefined,
                  age: age || undefined,
                },
                size: rowsPerPage,
                after: null,
           });
        }catch{
            throw new Error("Something is wrong");
        }

    },
    [refetch],
  );

const handleValidateError = () => console.warn('some value in form goes wrong.')

// 略

<form onSubmit={handleSubmit(handlePersonRefetch)}>
    <TextField
        label="Name"
        variant="outlined"
        {...register('name')}
    / >

    <TextField
       label="Age"
       variant="outlined"
       {...register('age')}
    / >

   {/* form的預設行為是submit,所以按了這個鈕就會自動驗證表單內容是否合法 */}
   {/* 如果合法的話就呼叫handlePersonRefetch */}
   <Button type="submit" variant="contained">Search</Button>
</form>


getValues & setValue

兩者皆為useForm的回傳值內的屬性

getValues可用取得用register註冊過的欄位的值,setValue則是設定註冊過的欄位的值

disabled的input,使用getValues取值會得到undefined,因此官方建議用readonly

// 上略
const { register, getValues } = useForm<FormField>();

// 略
<TextField
    label='name'
    variant='outlined'
    {...register('name')}
/>

// getValues會得到undefined
<TextField
    label='age'
    variant='outlined'
    disabled={disabled}
    {...register('age')}
/>

<TextField
   type="text"
   defaultValue={25}
   variant="outlined"
   inputProps={{ readOnly: true }}
   {...register('age')}
/>

<Button type="button" onClick={() => {
    console.log(getValues(['name', 'age']));
}}>get values</Button>


<Button type="button" onClick={() => {
    setValue('name', 'Tempura Ninja');
}}>set name</Button>


參考資料

Performance of React Hook Form
Day 10 - 為什麼要用 React Hook Form

Turn Anything Into A Form Field With React Hook Form Controller


#React #react-hook-form







Related Posts

command line,那個電影世界裡駭客電腦上的黑色小視窗

command line,那個電影世界裡駭客電腦上的黑色小視窗

module.exports 的運用

module.exports 的運用

Secure Apache Using Certbot with Let's Encrypt on Ubuntu 20.04

Secure Apache Using Certbot with Let's Encrypt on Ubuntu 20.04


Comments