目錄
React hook form是什麼
在這篇提到過由於用uncontrolled component
來做表單滿髒
的,所以官方推薦
盡量使用controlled component
然而controlled component
有2個問題
- 每次資料更新就會呼叫setState,這會導致一直re-render,而導致
效能低落
- 如果表單有多個欄位就會有多個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