表單在 react 中的處理相當的繁複,每個 input 都要一個 onCharge,還要一個一個上,我剛學如何處理的時候也吃了不少苦頭,直到我遇見 react-hook-form,今天就來試試這個套件如何幫助我們處理表單。
因為要做新增 todo,所以我們肯定就需要一個表單來輸入資料了,在昨天的進度當中我們已經做好的一個跳窗裡面能夠新增 todo 的 UI :
<form>
包住全部的 <input>
使用 <form>
來包所有的 <input>
,也包含送出表單的按鈕。
以下是 MDN 的範例,點進去網頁就能夠自己動手玩一玩。
<form action="" method="get" class="form-example">
<div class="form-example">
<label for="name">Enter your name: </label>
<input type="text" name="name" id="name" required />
</div>
<div class="form-example">
<label for="email">Enter your email: </label>
<input type="email" name="email" id="email" required />
</div>
<div class="form-example">
<input type="submit" value="Subscribe!" />
</div>
</form>
觀察這一組結構,其實會發現幾件事情 :
<label>
與 <input>
的 id當 <label>
與 <input>
的 id 對應上的時候,我們就不需要真的點選到 <input>
,只要點到 <label>
的時候,它的 <input>
就會進入 focus
狀態,這時候直接輸入就行了,相當方便。
注意到這個細節,並且確實的使用在自己的網頁上,會大大提升使用者在使用表單的體驗,多多利用吧。
讓我們來造訪一下 react-hook-form 的官方網站吧!
來安裝一下 :
npm install react-hook-form
觀察一下官方的範例 :
import { useForm } from "react-hook-form";
export default function App() {
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm();
const onSubmit = (data) => console.log(data);
console.log(watch("example")); // 監聽 input name example
return (
/* "handleSubmit" will validate your inputs before invoking "onSubmit" */
<form onSubmit={handleSubmit(onSubmit)}>
{/* register your input into the hook by invoking the "register" function */}
<input defaultValue="test" {...register("example")} />
{/* include validation with required or other standard HTML validation rules */}
<input {...register("exampleRequired", { required: true })} />
{/* errors will return when field validation fails */}
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
);
}
我們會發現,要先使用 useForm() Hook,裡面提供了一些方法 :
register
在 input 中綁定,讓 onSubmit 時可以取得個別 input 資料。handleSubmit
顧名思義就是處理表單送出 (submit) 時需要做的事情。TodoPage.tsx
import { useForm } from "react-hook-form";
const TodoPage: React.FC = () => {
...
const {
register,
handleSubmit,
watch,
reset,
formState: { errors },
} = useForm();
...
}
register
與 handleSubmit
...
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grid grid-cols-1 gap-[8px] w-[340px]">
<label className="block ">
<span className="text-gray-700">標題</span>
<input
type="text"
className="
mt-1
block
w-full
rounded-md
bg-gray-100
border-transparent
focus:border-gray-500 focus:bg-white focus:ring-0
"
{...register("title", { required: true })}
/>
</label>
<label className="block">
<span className="text-gray-700">時間</span>
<input
type="date"
className="
mt-1
block
w-full
rounded-md
bg-gray-100
border-transparent
focus:border-gray-500 focus:bg-white focus:ring-0
"
{...register("time")}
value="2018-07-22"
/>
</label>
<label className="block">
<span className="text-gray-700">詳情描述</span>
<textarea
className="
mt-1
block
w-full
rounded-md
bg-gray-100
border-transparent
focus:border-gray-500 focus:bg-white focus:ring-0
"
rows={3}
{...register("info")}
/>
</label>
<label className="flex justify-center items-center">
<input
type="submit"
className="
mt-1
block
w-full py-[4px]
cursor-pointer
rounded-md
bg-gray-300
border-transparent
focus:border-gray-500 focus:bg-white focus:ring-0
"
/>
</label>
</div>
</form>
...
...
const onSubmit = (data) => console.log(data);
...
能夠拿到表單資料之後,可以來撰寫增加 todo 功能了!
首先,我們需要一個 state,是用來裝清單的內容,也就是要繞進表格裡的資料,長相如下 :
const [list, setList] =
useState <
any >
(() => [
{ time: "2018-07-22", title: "購物", info: "日用品", checked: false },
{ time: "2022-09-15", title: "鐵人賽", info: "day-10", checked: true },
{ time: "2022-09-25", title: "摺棉被", info: "", checked: false },
]);
有了 list 之後,我們的畫面要跟 list 的內容有關係,先把內容跟 UI 綁在一起。
在這邊使用的是 JS 的 map 來做遍歷 list,並且把資料放到畫面上 :
...
<Tbody>
{list.map((i, key) => (
<Tr key={key}>
<Td>
<Checkbox isChecked={i.checked} />
</Td>
<Td>{i.title}</Td>
<Td>{i.info}</Td>
<Td className="flex items-center">
<span>
<svg
width="20"
height="20"
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.75 21.825V25.625C3.75 25.975 4.025 26.25 4.375 26.25H8.175C8.3375 26.25 8.5 26.1875 8.6125 26.0625L22.2625 12.425L17.575 7.73748L3.9375 21.375C3.8125 21.5 3.75 21.65 3.75 21.825V21.825ZM25.8875 8.79998C26.375 8.31248 26.375 7.52498 25.8875 7.03748L22.9625 4.11248C22.475 3.62498 21.6875 3.62498 21.2 4.11248L18.9125 6.39998L23.6 11.0875L25.8875 8.79998Z"
fill="black"
/>
</svg>
</span>
<span>
<svg
width="25"
height="25"
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.39946 24.6985C8.39946 26.0848 9.53373 27.2191 10.9201 27.2191H21.0024C22.3888 27.2191 23.523 26.0848 23.523 24.6985V9.57494H8.39946V24.6985ZM11.4998 15.7252L13.2768 13.9482L15.9612 16.62L18.6331 13.9482L20.4101 15.7252L17.7383 18.397L20.4101 21.0689L18.6331 22.8459L15.9612 20.174L13.2894 22.8459L11.5124 21.0689L14.1842 18.397L11.4998 15.7252ZM20.3723 5.79404L19.112 4.53374H12.8105L11.5502 5.79404H7.13916V8.31464H24.7833V5.79404H20.3723Z"
fill="black"
/>
</svg>
</span>
</Td>
</Tr>
))}
</Tbody>
...
好,這樣我們的畫面跟 list 這個 state 同步了,這意味著,我們只要增加 list 中的項目,就可以改變畫面了!
我們什麼時候要增加 todo 進去 list 呢?那就是在送出表單的時候,所以回到 onSubmit,我們在裡面做一些事情 :
setList()
改變 list 的內容...
const onSubmit = (data) => {
setList([...list, data]); // 更新 list 把表單的 data 放進去
if (isOpen) setIsOpen(false); // 更新完之後關掉跳窗
console.log("list", list); // 測試看看資料有沒有被新增
};
...
新增功能做好了,但測試的時候會發現,舊的內容還會被保存在表單裡面,接下來我們就來處理這個狀況吧。
首先,先從 useFrom()
裡面把 reset()
取出來 :
...
const {
register,
handleSubmit,
watch,
reset, // 在這裡喔!
formState: { errors },
} = useForm();
...
再來就是用,很簡單的 XD
...
const onSubmit = (data) => {
setList([...list, data]);
if (isOpen) setIsOpen(false);
reset(); // 在這裡喔!
console.log("list", list);
};
...
使用 react-hook-form 有效的解決在 react function component 中綁定表單的麻煩,一用成主顧,真的讓表單處理變得比較簡單了。