简体   繁体   中英

Invalid hook call when trying to fetch data using useCallback

I'm trying to call useState inside an async function like:

const [searchParams, setSearchParams] = useState({});

const fetchData = () => useCallback(
    () => {
      if (!isEmpty(searchParams)) {
        setIsLoading(true); // this is a state hook
        fetchData(searchParams)
          .then((ids) => {
            setIds(ids); // Setting the id state here
          }).catch(() => setIsLoading(false));
      }
    },
    [],
  );

There are two states I am trying to set inside this fetchData function ( setIsLoading and setIds ), but whenever this function is executed am getting the error:

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app

What is this Rule of hooks I am breaking here? Is there any way around to set these states from the function?

PS: I only used the useCallback hook here for calling this function with lodash/debounce

Edit: The function is called inside useEffect like:

const debouncedSearch = debounce(fetchSearchData, 1000); // Is this the right way to use debounce? I think this is created every render.

const handleFilter = (filterParams) => {
    setSearchParams(filterParams);
};

useEffect(() => {
    console.log('effect', searchParams); // {name: 'asd'}
    debouncedSearch(searchParams); // Tried without passing arguments here as it is available in state.
// But new searchParams are not showing in the `fetchData`. so had to pass from here.
}, [searchParams]);

The hook rule you are breaking concerns useCallback because you are returning it as the result of your fetchData;

useCallback should be called on top level; not in a callback, like this:

const fetchData = useCallback(
    () => {
      if (!isEmpty(searchParams)) {
        setIsLoading(true); // this is a state hook
        fetchData(searchParams)
          .then((ids) => {
            setIds(ids); // Setting the id state here
          }).catch(() => setIsLoading(false));
      }
    },
    [],
  );

The code you wrote is equivalent to

const fetchData = () => { return React.useCallback(...

or even

function fetchData() { return React.useCallback(...

To read more about why you can't do this, I highly recommend this blog post .

edit:

To use the debounced searchParams , you don't need to debounce the function that does the call, but rather debounce the searched value. (and you don't actually the fetchData function that calls React.useCallback at all, just use it directly in your useEffect )

I recommend using this useDebounce hook to debounce your search query

const [searchParams, setSearchParams] = React.useState('');
const debouncedSearchParams = useDebounce(searchParams, 300);// let's say you debounce using a delay of 300ms

React.useEffect(() => {
  if (!isEmpty(debouncedSearchQuery)) {
        setIsLoading(true); // this is a state hook
        fetchData(debouncedSearchParams)
          .then((ids) => {
            setIds(ids); // Setting the id state here
          }).catch(() => setIsLoading(false));
      }
}, [debouncedSearchParams]); // only call this effect again if the debounced value changes

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM