最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React useCallback linting error missing dependency - Stack Overflow

programmeradmin3浏览0评论

I am using a custom hook useInstantSearch in my ponent.

When I wrap it in useCallback to I get the following error:

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

This is the code:

  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]);

So effectively to send the updated search term to a child ponent for display only then the parent handles the debouncing of the search when that term changes.

search, cancelTrigger, searchStillValid

Are not part of the parent ponent, they are part of useInstantSearch.

Is this a warning I can ignore?

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 ponent.
 */
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);
  };
}

I am using a custom hook useInstantSearch in my ponent.

When I wrap it in useCallback to I get the following error:

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

This is the code:

  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]);

So effectively to send the updated search term to a child ponent for display only then the parent handles the debouncing of the search when that term changes.

search, cancelTrigger, searchStillValid

Are not part of the parent ponent, they are part of useInstantSearch.

Is this a warning I can ignore?

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 ponent.
 */
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);
  };
}
Share Improve this question edited Jul 10, 2020 at 13:05 RyanP13 asked Jul 10, 2020 at 12:29 RyanP13RyanP13 7,75328 gold badges101 silver badges170 bronze badges 2
  • Is it safe to use hook within another hook? – user0101 Commented Jul 10, 2020 at 12:30
  • I am not sure how else to use it. I need to update searchTerm immediately for the user but then debounce the call to the API? – RyanP13 Commented Jul 10, 2020 at 12:33
Add a ment  | 

2 Answers 2

Reset to default 8

Since you're using some external function, you can simply ignore the message:

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

However, you should be using it like:

  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 ment is required here because, you're using callback inside useInstantSearch as there's no way to inject them as dependency.

You can ignore it if you don't mind the stale closures you can do it that way:

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 ponent.
 */
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,
          () =>
            //paring 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./ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare./ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

There may be a solution to your problem but without knowing what useInstantSearch is it's impossible to provide one.

My guess is that you should use useCallback inside useInstantSearch but since that code is missing from your question I can only guess.

发布评论

评论列表(0)

  1. 暂无评论