John Spiegel
John Spiegel

Reputation: 1903

What causes lodash debouncer "TypeError: Expected a function" error?

I'm attempting to use lodash's debouncer method in a react function component. When I attempt to create the debouncer-wrapped callback, I receive

TypeError: Expected a function

at the point of the useCallback statement. I've attempted to work through three or four examples, but none of them work within my existing app. The latest attempt I'm working through is here.

  const [userQuery, setUserQuery] = useState("")
  const [deb, setDeb] = useState("");

  const updateQuery = () => {
      setDeb(userQuery)
      };
      
  //const delayedQuery = useCallback(debounce(updateQuery, 500), []);
  //const delayedQuery = useCallback(debounce(updateQuery, 500), [userQuery]);

  const delayedQuery = useCallback(() => debounce(updateQuery, 500), [userQuery]);
  
  const onChange = e => {
     setUserQuery(e.target.value);

  };
  
  useEffect(() => {
     delayedQuery();
  
     // Cancel the debounce on useEffect cleanup.
     return delayedQuery.cancel;
  }, [userQuery, delayedQuery]);

The code functions overall without the debounce wrapper and related return in the useEffect (without any debouncing, of course).

Other variations have are shown in comments, but all lead to the same error.

I'm at a level of modestly comfortable with React so have a lot of gaps in my deeper understanding that could be the problem.

Edit The message reads to me as though debounce isn't recognized as being a function. I receive no indications of issues attempting to import debounce using either import { debounce } from 'lodash/has'; or import { debounce } from 'lodash/fp';

Error results: enter image description here

Upvotes: 0

Views: 1077

Answers (3)

John Spiegel
John Spiegel

Reputation: 1903

The issue was importing the wrong lodash function. Specifically, I was originally importing:

import { debounce } from 'lodash/fp';

which (from my partial understanding) is a wrapper that better applies to functional programming techniques.

In this case, what I needed was to import:

import { debounce } from 'lodash';

Upvotes: 1

Janik
Janik

Reputation: 698

I assume the mentioned TypeError was thrown during the useEffect, since you have not provided any Stack Trace.

Edit thanks to the comment and suggestion by @Ori Drori: Change the delayedQuery to the following:

const delayedQuery = useCallback(debounce(updateQuery, 500), []);

This way, you will have a memoized value of your debounced method, which will be a callback anyway, with the available cancel property.


Previous Answer (just for reference):

Then you can try changing your useEffect to the following:

useEffect(() => {
  const query = delayedQuery();
  query();
  
  // Cancel the denounce on useEffect cleanup
  return query.cancel;
}, [userQuery, delayedQuery]);

Explanation

You can have a look at the different types of your methods and callbacks. I‘ll start at the beginning:

  1. useCallback simply takes any method and returns a new method, which will simply invoke the provided method. It will match the signature of the provided method.
  2. The provided callback, in turn, is a simple arrow function, which returns a debounced method.
  3. The debounced method, according to the lodash docs, can either be called immediately, canceled or flushed.

This leads us to the following in your useEffect:

  • delayedQuery is a callback.
  • You call delayedQuery(), where the hook invokes the arrow function.
  • The arrow function invokes the debounce and returns its value — the debounced updateQuery!
  • BUT: at this point, your query has not yet been invoked! Moreover, since only the return type of your callback is a debounced function, there is no property delayedQuery.cancel.
  • And because delayedQuery.cancel == undefined, you get the mentioned TypeError.

Upvotes: 1

Ori Drori
Ori Drori

Reputation: 191976

When you call delayedQuery() you return the debounced function, and not the result of calling the debounced function. Since debounce returns a debounced function with the cancel method, and useCallback accepts a function, define delayedQuery like this:

const delayedQuery = useCallback(debounce(updateQuery, 500), []);

Upvotes: 2

Related Questions