[筆記] 利用 React useContext & useReducer 來完成通用表單元件

標題看起來好複雜

YY
10 min readAug 28, 2021

作為一個內部管理系統,總有著一堆看起來 87% 像的頁面針對不同資源做 CRUD,像是使用者管理清單、權限清單,頁面終究會有張 table 來顯示這些清單,另外會有個 search/filter 的區塊來做快速查找

而這個搜尋的區塊在不同的頁面會有不同的欄位以及欄位型態 (文字、數字、日期、email、選單等等),那該如何有效率的重用這個 search form 讓每一頁的搜尋區塊能保持 UI 及邏輯的一致且當日後要新增其他資源的操作頁面也能快速完成搜尋功能

正因為最近工作遇到類似需求,需要產出大量頁面來針對不同資源進行操作,但在每一頁都需要重複處理 search form 這樣的元件,也可能因為不同成員開發導致 UI 或操作邏輯上的不一致,因此粗糙地產出了個輕量的可重用解決方案,在此先做個筆記

需求

如上圖,當有一個頁面要針對「課程」資源進行操作,那頁面中通常會有 search form 與 data table 這兩塊核心元件,而在開發上方搜尋區塊時可能會長成這樣:

透過 controlled form 來處理這些 input fields,因而需要一大堆的 state 以及 value change handler

雖然都不是難事,但如果有其他「課程」以外的資源也需要個頁面來讓使用者操作,那每頁都要花費這些時間來針對不同的欄位與型態處理 state 與 event handler

因此我們需要的一個可以通用在各種資源操作頁面的 search form,並且能根據每一頁的需求輕鬆放進不同的搜尋欄位及不同的欄位型態,讓 search from 能管理好自己的 state,而元件使用者只需要放好對的欄位並靜待 search from 觸發 submit 時把每個欄位的值送出來

如同下圖,亦是本篇 demo 成品

定義規格

首先,需要一個 search form 的主元件,並且能夠透過監聽 submit 事件取得 form 裡所有欄位的值,於是大概有了這樣的畫面

但在不同頁面會有不同的搜尋欄位要顯示,勢必要讓 SearchForm 能接收到這些資訊,所以也許可以嘗試透過 props 塞進特定結構的欄位清單,讓 SearchForm 自行判別並產生對應型態的欄位

不過透過這種結構化資料的形式來定義 search form 裡的欄位在可讀性上其實沒那麼好,code 也會變得很冗長,所以希望他長成如同 React Bootstrap 的使用形式,能夠容易理解在 form 裡擺了什麼 input component

source: https://react-bootstrap.netlify.app/components/forms/#forms-select

因此結論就是,除了一個主要的 SearchForm,並由 SearchForm 提供包裝過的 input, select, checkbox 等欄位

實作

這邊將直接拿下方 demo project 的 code 來做講解

UI 製作

首先為了方便 demo 這邊使用了 react-bootstrap 提供的 Form 元件並以 styled-components 來處理樣式,而在 /src/components/SearchForm/index.ts 可以看到有一個 SearchForm 本體以及提供了 InputSelect 兩種型態的欄位可以使用

在 SearchForm 本體的部分可以看到僅有一個 Container 裡面包了個 Fields 來裝 children 也就是 input 欄位,而後提供了兩個分別為 search 及 clear 的按鈕,props 僅有 onSearchonClear ,也就是當 search button 或 clear button 被按下時的 callback

接著 Input 與 Select 元件,皆透過 Field 來包住名稱與欄位元件,且每種欄位都使用 fieldLabelfieldKey props 來顯示欄位名以及作為欄位索引

Select 則另外多了 options props 讓使用者可以傳入欄位選項的 label/value 列表

如此一來我們就可以任意產生一個帶有 search button 及 clear button 的search form 並任意在裡面放入各式欄位

State 管理

在有了 UI 後則是要讓 SearchForm 能夠正確管理其底下每個欄位的值,並且在 onSearch 時能夠把正確的資料傳出去,為了要讓 SearchForm 能取得所有欄位,將會在 SearchForm 建一個 fields state,並透過 useContext 讓底下的欄位可以拿到對應的值來做 controlled form

首先要建立個 context 讓 SearchForm 能把這個 fields state 丟進去,並且讓 Input、Select 這些包裝過的專用欄位能取得,於是有了個 SearchFormContext,其中 fields 會是以 { fieldKey: fieldValue } 的形式來記錄每個欄位,並 export context provider 以及 useSearchFormField hook 讓 SearchForm 裡的欄位能透過這個 hook 取得其值

最後,當使用者在操作欄位時我們需要把新的值寫回 SearchForm 的 fields state,在上方的 code 可以看到這邊是使用 useReducer 來產生這個 state 以及 dispatcher,再包裝成 setFieldValue function 塞進 context 給底下欄位使用

首先先建立 reducer 以及欄位會需要的 action creators,就可以把整個 state 的操作流程串起來了

大功告成,如此一來之後要新增任何資源的操作頁面就可以直接塞入這個 SearchForm,並依照需求放置所需要的欄位,若有不同於 Input、Select 欄位的需求也只要依照同樣模式包裝出新型態的欄位即可任意使用

最後,這只是一個菜雞前端突發奇想的做法,如果你剛好有這種需求卻不知道如何下手則可以參考看看,若你有更完美的解決方案也歡迎分享討論

--

--