React useCallback ошибка линтинга отсутствует зависимость - PullRequest
6 голосов
/ 10 июля 2020

Я использую специальный хук useInstantSearch в своем компоненте.

Когда я оборачиваю его в useCallback, я получаю следующую ошибку:

React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead.

Это код :

  const [searchTerm, setSearchTerm] = useState<string>(searchQuery);
  const handleDebouncedSearch = useCallback(
    useInstantSearch(searchTerm, (search, cancelTrigger, searchStillValid) => {
      console.log('instant search', search);
    }),
    []
  );

  useEffect((): void => {
    handleDebouncedSearch(searchTerm);
  }, [searchTerm, handleDebouncedSearch]);

Таким образом, чтобы отправить обновленный поисковый запрос дочернему компоненту для отображения только тогда, когда родительский элемент обрабатывает отклонение поиска при изменении этого термина.

search, cancelTrigger, searchStillValid

Не часть родительского компонента, они являются частью useInstantSearch.

Это предупреждение, которое я могу игнорировать?

import { useEffect, useRef } from 'react';
import { CancelTrigger } from '../../../ars/api/api.cancel';

const DELAY_SEARCH_MS = 300;

interface InstantSearchOnChange {
  (search: string, cancelTrigger: CancelTrigger, resultStillValid: { (): boolean }): void;
}

/**
 * Helper to delay request until user stop typing (300ms), support deprecated requests (cancel and helper to not update the state), or unmounted component.
 */
export default function useInstantSearch(initialSearch: string, onChange: InstantSearchOnChange): { (value: string): void } {
    
  const search = useRef<string>(initialSearch);
  const requests = useRef<CancelTrigger[]>([]);

  const mounted = useRef<boolean>(true);

  useEffect(() => {
    return (): void => {
      mounted.current = false;
    };
  }, []);

  return value => {
    search.current = value;

    setTimeout(() => {
      if (search.current === value) {
        requests.current = requests.current.filter(r => !r.cancel());

        const trigger = new CancelTrigger();
        requests.current.push(trigger);

        onChange(value, trigger, () => search.current === value && mounted.current);
      }
    }, DELAY_SEARCH_MS);
  };
}

Ответы [ 2 ]

1 голос
/ 10 июля 2020

Поскольку вы используете какую-то внешнюю функцию, вы можете просто проигнорировать сообщение:

useCallback(
  useInstantSearch(...)
, []) // eslint-disable-line react-hooks/exhaustive-deps

Однако вы должны использовать его как:

  const [searchTerm, setSearchTerm] = useState<string>(searchQuery);
  const handleDebouncedSearch = useCallback(() => { // -> this
    useInstantSearch(searchTerm, (search, cancelTrigger, searchStillValid) => {
      console.log('instant search', search);
    })
  }, [searchTerm]); // eslint-disable-line react-hooks/exhaustive-deps

Здесь требуется комментарий Eslint, потому что вы используете обратный вызов внутри useInstantSearch, поскольку нет возможности внедрить их как зависимость.

1 голос
/ 10 июля 2020

Вы можете проигнорировать это, если не возражаете против устаревших закрытий , вы можете сделать это таким образом:

const { useRef, useCallback, useEffect } = React;
const DELAY_SEARCH_MS = 300;
const later = (value, time) =>
  new Promise((resolve) =>
    setTimeout(() => resolve(value), time)
  );
/**
 * Helper to delay request until user stop typing (300ms), support deprecated requests (cancel and helper to not update the state), or unmounted component.
 */
function useInstantSearch(onChange) {
  const timeout = useRef();
  const mounted = useRef(true);
  useEffect(() => {
    return () => {
      mounted.current = false;
    };
  }, []);
  return useCallback(
    (value) => {
      clearTimeout(timeout.current); //cancel other
      timeout.current = setTimeout(() => {
        const current = timeout.current;
        onChange(
          value,
          () =>
            //comparing timeout.current with current
            //  async function may not be the last to resolve
            //  this is important when you want to set state based
            //  on an async result that is triggered on user input
            //  user types "a" and then "b" if 2 async searches start
            //  "a" and "ab" and "a" is so slow it will resolve after "ab"
            //  then state result will be set for "ab" first and then with "a"
            //  causing UI to be out of sync because user searched for "ab"
            //  but results for "a" are shown
            timeout.current === current && mounted.current
        );
      }, DELAY_SEARCH_MS);
    },
    [onChange]
  );
}

const App = () => {
  const handler1 = useCallback(
    (value) => console.log('handler1:', value),
    []
  );
  const handler2 = useCallback(
    (value) => console.log('handler2:', value),
    []
  );
  const handler3 = useCallback((value, shouldResolve) => {
    console.log('starting async with:', value);
    return later(
      value,
      value.length === 1 ? 1000 : 800
    ).then(
      (resolve) =>
        shouldResolve() &&//you can opt not to set state here
        console.log('resolved with', resolve)
    );
  }, []);
  const debounced1 = useInstantSearch(handler1);
  const debounced2 = useInstantSearch(handler2);
  const debounced3 = useInstantSearch(handler3);
  [1, 2, 3].forEach(
    (num) =>
      setTimeout(() => {
        debounced1(num);
        debounced2(num * 2);
      }, 100) //lower than the 300 of debounce
  );
  //both callbacks will be called but "a" resolves after "ab" since
  //  "ab" was the last to be requested it will be the only one that logs
  //  resolved with
  setTimeout(() => debounced3('a'), 500);
  setTimeout(() => debounced3('ab'), 1500);
  return 'hello world (check console)';
};

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Возможно, у вашей проблемы есть решение, но, не зная, что такое useInstantSearch, его невозможно предоставить.

Я предполагаю, что вы должны использовать useCallback внутри useInstantSearch, но поскольку этот код отсутствует в вашем вопросе, я могу только догадываться.

...