俗話說的好:Never trust user input,開發時設想好的流程,清楚的畫面,適當的輔助文字,使用者仍然還是會有「意想不到」的操作。
今天來面對其中一種使用者,工作速度很快,打字一分鐘三五百行,並對你建立的 AutoComplete 搜尋功能愛不絕口,只要簡單輸入幾個關鍵字就能把資料庫的資料列出來,但使用者疾風般打字速度真的是...
後端:「饒了我可以嗎 :)」
AutoComplete 指的是一種進階的 input,可以在使用者輸入文字時提供建議(或固定)的選項,不論是hard code或是透過API抓資料呈現,都是很好的搭配良器。
假設今天是串API取得的資料,可以透過輸入的 input 當作query,並將回傳的資料呈現出來供使用者選擇,但我們不想要每顆按鍵當要做一次request,因此我們加入 debounce 並搭配 react-hook 實做出來。
Chakra-ui 沒有內建好的 AutoComplete,DEMO是將既有的元件簡單拼起來 (〒︿〒)
簡單拼起來後,AutoComplete 能接受這些 prop
Prop | Type | Description |
---|---|---|
data |
array | 呈現列表的資料,當為長度為0時會呈現no option的字樣 |
inputValue |
any | 控制 input 的 value |
onInputChange |
function | 當input改變時執行的callback |
value |
any | 給予 AutoComplete 本身的 value,當 value 有出現在呈現的列表時會額外highlight |
onChange |
function | 當點擊 item 時會執行的 callback |
isLoading |
boolean | 當 true 時,會進入 loading 的狀態並呈現讀取條 |
目前的行為是每個 key-storke 都會進行一次 request:
function Example() {
const [choose, setChoose] = useState("")
const [input, setInput] = useState("")
const users = getUsers({ name: input, limit: 5 })
return (
<AutoComplete
data={users}
inputValue={input}
onInputChange={setInput}
value={choose}
onChange={setChoose}
/>
)
}
getUsers
模仿API抓取資料,接受兩種 query:name (搜尋名字) & limit (回傳筆數)input
是給輸入使用的 statechoose
是保存選取的內容useState
- 保存 setTimeout 本身是否正在執行/已執行的狀態useEffect
- 處理 setTimeout 流程,並透過傳入 deps 當作 trigger,clean-up 則是component un-mount 時 pending 的 setTimeout 也能一起被取消useRef
- 處理是否為第一次 mount,來略過第一次的執行整體如下:
function useDebounce(cb, delay = 250, deps) {
const initRef = useRef(true)
const [isPending, setIsPending] = useState(false)
useEffect(() => {
if (initRef.current) {
initRef.current = false
return
}
setIsPending(true)
const timeout = setTimeout(() => {
cb()
setIsPending(false)
}, delay)
return () => clearTimeout(timeout)
}, deps)
return isPending
}
Param | Type | Description |
---|---|---|
cb |
function | callback,當 setTimeout 的 delay 時間到時會觸發的動作 |
delay |
number | 單位ms , 定義 setTimeout 要 delay 多久,default 為 250ms |
deps |
array | useEffect 的 deps,來決定要不要執行 |
Return | Type | Description |
---|---|---|
isPending |
boolean | 呈現 setTimeout是否執行的狀態 |
function Example() {
const [choose, setChoose] = useState("")
const [input, setInput] = useState("")
const [delayInput, setDelayInput] = useState("")
const isLoading = useDebounce(() => setDelayInput(input), 2000, [input])
const users = getUsers({ name: delayInput, limit: 5 })
return (
<AutoComplete
data={users}
inputValue={input}
onInputChange={setInput}
value={choose}
onChange={setChoose}
/>
)
}
我們另外建立 delayInput
, 並把 setDelayInput
放入到 useDebounce
,設定 2000ms,並在 deps
加入 input
當作觸發。
這樣一來,疾風打字使用者打在快也不怕了!
設計上較依賴其他的 state 來決定是否要觸發,假如不放入 deps,每次 render 就會執行一次。
功能延伸上可以再搭配快取或是 SWR 來記住曾經打過的 Request,面對重複的搜尋就不用再讓使用者等待過久。