反应useCallback linting错误缺少依赖项

时间:2020-07-10 12:29:47

标签: javascript reactjs react-hooks

我在组件中使用了自定义钩子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 个答案:

答案 0 :(得分:1)

如果您不介意stale closures,则可以忽略它,可以这样做:

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,但是由于您的问题中缺少该代码,我只能猜测。

答案 1 :(得分:1)

由于您正在使用某些外部功能,因此您可以简单地忽略以下消息:

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中使用回调,因为无法将它们作为依赖项注入。