google-map-react Autocomplete


Posted by Rich on 2021-09-04

發現錯誤 請勿參考本文內容 待後續修改

終於把 debounce 的功能加到搜尋欄裡面了!當初一度要放棄,先用按鈕做代替。後來突破盲腸,終於做出有 autocomplete 的搜尋列。在 react 裡面實作這項功能對我這個萌新而言,比較複雜。而且很多作法是從來沒想過的。
第一個沒想到的事,我以為會直接用 useState 去控制輸入進搜尋列的值。像這樣:

const [inputValue, setInputValue] = useState("")

<input value={inputValue} onChange={setInputValue} />

但實際根據 Clay 助教的寫法會是這樣:

let input = useRef("")
const [inputText, setInputText] = useState("")

<input ref={input} onChange={() => {
    setInputText(input.current.value)
}}

我蠻好奇這種寫法除了 debounce 以外的其他使用時機。至於為什麼要這樣寫? 看到後面應該就會知道了。
最後再配上這個:

useEffect(() => {
callapi()
},[inputText])

就可以在 inputText 改變的時候去 callapi。
這樣也可以跑,但就是每次輸入改變的時候,都會 call api。我們當然不希望這樣(尤其當你是 google api 的免費仔,call 的數量有限)。
在這之前還是先試試看 react 裡面可不可以好好 debounce。我去載了 lodash 這個套件,裡面有 debounce 這個方法讓我們輕鬆 debounce。

import _ from "lodash";
const debounced = _.debounce(??,1000)

這時候我卡住了,我一開始去 debounce callapi 的部份,很直覺的想法,因為我只想送出一個 request。但這樣有兩個問題。

  • 第一個 react 裡面用 debounce 要用 useCallback 這個 hook。原因的話,我自己的理解是,每次 react 渲染頁面都會重新呼叫一次函式。那我們在函式裡面的 const debounced = _.debounce(??) 也會被重新宣告一次。那這樣上一次呼叫的 debounced 和這次呼叫的 debounced 就不會是同個函式。 如果大概了解過 debounce 就知道。我們應該是呼叫同個函式多次才會得到 debounce 的效果。所以變成這樣:

    const debounced = useCallback(_.debounce(??,1000),??)
    

    useCallback 的第二個參數是,當這個參數改變的時候,會回傳不同的函式。當初我耍蠢放進 [inputText],然後就沒有 debounce 了。這裡應該會希望不管怎樣都會傳同樣的函式,所以放 []

    const debounced = useCallback(_.debounce(??,1000),[])
    
  • 第二個問題是要 debounce 什麼,前面説到是這樣

    const debounced = useCallback(_.debounce(callapi(),1000),[])
    

    看起來合理,等一秒沒打字再 callapi。那要什麼時候呼叫 debounced? 前面的 useEffect 的 callapi 變成 debounced。所以是 inputText 改變的時候,就呼叫 debounced。

    useEffect(() => {
    debounced()
    },[inputText])
    

    但我用了之後發現有個問題。debounced 回傳的函式都是一樣的。「咦?前面才說要一樣啊」一樣的話代表 callapi() 裡面的參數也是一樣的。我們這次要做的是 autocomplete 的效果。所以隨著 inputText 改變,callapi 裡面的參數也要改變(要戴上使用者輸入去搜尋)。但現在放進 useCallback 裡面我不會改了。輾轉到最後,就用 Clay 教學裡面的方法。

就是 debounce 的不是 callapi ,是 setInputText

let input = useRef("")
import _ from "lodash";
const [inputText, setInputText] = useState("")

<input ref = {input}  onChange={() => {
debounced()
}}/>
const debounced = useCallback(_.debounce(setInputText(input.current.value),1000),[])

useEffect(() => {
callAPI()
},[inputText])

每次 input 的值改變的時候都會呼叫 debounced ,這時候真正被 debounce 的是 setInputText。所以在打字停止一秒這之前,inputText 都維持初始值,沒有改變。停止打字一秒後,inputText 才真正被改變。而被改變時,也觸發 useEffect,所以去 callapi。真是漂亮的設計!
結果長這樣:

有任何錯誤歡迎指正,感謝觀賞。

參考的教學










Related Posts

[第十周] 有點難分的 Session 和 Cookie

[第十周] 有點難分的 Session 和 Cookie

W13_現代前端工具、JS 與 CSS 補充概念_學習筆記整理

W13_現代前端工具、JS 與 CSS 補充概念_學習筆記整理

Ruby: self

Ruby: self


Comments