Дебюанс (функция огня только один раз в несколько секунд) с реагирующими крючками - PullRequest
1 голос
/ 11 марта 2019

Мне нужно реализовать поиск по тегам при вводе пользователем, но ввод происходит быстро, и я не хочу запускать вызов БД для каждого набранного пользователем символа, поэтому я был любопытен, есть ли простой хороший способ отменить вызовы API, давайтескажем - один раз с задержкой в ​​3 секунды?

А пока я придумываю следующее:

  let searchDelay
  async function handleTagSearch(e) {
    clearTimeout(searchDelay)

    tagSearchPhraseSet(e.target.value)

    searchDelay = setTimeout(async () => {
      if (e.target.value.length > 3) {
        let res = await fetch('api/tag_seatch/' + e.target.value)
        res = await res.json() 

        console.log(res)
      }
    }, 3000)
  }

Но правильный ли это подход?

Ответы [ 3 ]

2 голосов
/ 11 марта 2019

Ваше решение выглядит многообещающе, если вы убедитесь, что число searchDelay сохраняется между рендерами, например, с. useRef крючок.

Еще один способ сделать это - использовать useEffect ловушку, которая запускается каждый раз, когда изменяется value. Из функции, заданной для useEffect, вы можете вернуть функцию, которая очищает время ожидания предыдущего запуска.

Пример

const { useState, useEffect } = React;

function App() {
  const [value, setValue] = useState("");
  const [result, setResult] = useState(null);

  useEffect(
    () => {
      if (value.length < 3) {
        setResult(null);
        return;
      }

      const timeout = setTimeout(() => {
        setResult(Math.random());
      }, 3000);

      return () => clearTimeout(timeout);
    },
    [value]
  );

  return (
    <div>
      <input value={value} onChange={e => setValue(e.target.value)} />
      <div>{result}</div>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
1 голос
/ 11 марта 2019

Благодаря примеру @Tholle я понял, что qoute «Функция, возвращаемая из функции useEffect, будет вызвана every time it is run again, и при размонтировании, как вы говорите», и придумал это решение:

import React, { useState, useContext, useEffect, useRef } from 'react'

export function TagsAdd() {
  const [searchTerm, searchTermSet] = useState('')
  const isFirstRun = useRef(true)

  useEffect(() => {
   //skip first run on component mount
   if (isFirstRun.current) {
      isFirstRun.current = false
      return
   }

    const timeout = setTimeout(() => {
      tagSearch(searchTerm)
    }, 2000) //2000 - timeout to execute this function if timeout will be not cleared

    return () => clearTimeout(timeout) //clear timeout (delete function execution)
  }, [searchTerm])

  // API call only one time in 2 seconds for the last value! Yeeeee
  async function tagSearch(value) {
    let res = await fetch('api/tag_seatch/' + value)
    res = await res.json()
    console.log(res)
  }

  //handle input change
  function handleInput(e) {
    searchTermSet(e.target.value)
  }

  return (
        <div>
          <input value={searchTerm} onChange={handleInput} />
        </div>
  )
}
0 голосов
/ 11 марта 2019

первое, что вам нужно рассмотреть, это использовать useCallback для запоминания, если вы просто напишите простую функцию, она будет повторно создаваться при каждом повторном рендеринге.IMO, вы должны использовать функцию отладки lodash вместо реализации своей собственной.результат выглядит примерно так:

const searchTags = useCallback(debounce(async evt => {
  const { value } = evt.target;
  if(value.length > 3){

    const response = await fetch('/api/tag_search', value);
    const result = await response.json();

    setTags(result) //or somewhere in your state
  }
}, 3000, { trailing: true, leading: false }));
...