J. Hesters
J. Hesters

Reputation: 14786

React Hooks: Is there a reason why leaving out the create or respectively the callback for useEffect and useCallback is bad?

So I was reading "A complete Guide to useEffect" by Dan Abramov and the Hooks docs.

In the article Dan gives the following example:

function SearchResults() {
  const [query, setQuery] = useState('react');

  // ✅ Preserves identity until query changes
  const getFetchUrl = useCallback(() => {
    return 'https://hn.algolia.com/api/v1/search?query=' + query;
  }, [query]);  // ✅ Callback deps are OK

  useEffect(() => {
    const url = getFetchUrl();
    // ... Fetch data and do something ...
  }, [getFetchUrl]); // ✅ Effect deps are OK

  // ...
}

And the Hook docs give this:

function ProductPage({ productId }) {
  // ✅ Wrap with useCallback to avoid change on every render
  const fetchProduct = useCallback(() => {
    // ... Does something with productId ...
  }, [productId]); // ✅ All useCallback dependencies are specified

  return <ProductDetails fetchProduct={fetchProduct} />;
}

function ProductDetails({ fetchProduct })
  useEffect(() => {
    fetchProduct();
  }, [fetchProduct]); // ✅ All useEffect dependencies are specified
  // ...
}

I was wondering: are callback and factory function really necessary?

You could also write:

const getFetchUrl = useCallback('https://hn.algolia.com/api/v1/search?query=' + query, [query]);

and

useEffect(fetchProduct, [fetchProduct]);

Similarly you could think of a scenario where you could leave the create function out for useMemo:

function Greeting({ name }) {
  const calculateExpensive = useCallback(() => {
    return `Hello ${name}`;
  }, [name]);

  const result = useMemo(calculateExpensive, [calculateExpensive]);
  return <p>{result}</p>;
}

I'm sure I'm doing a stupid mistake here. What am I not seeing and doing wrong?

Upvotes: 0

Views: 505

Answers (1)

Estus Flask
Estus Flask

Reputation: 222484

The reason for using useCallback is to keep the same reference to a function between renders when needed.

useCallback is supposed to accept callback function as an argument, as the name suggests. Since useCallback(fn) is a shortcut for useMemo(() => fn), it technically can be (mis)used with any argument:

const getFetchUrl = useCallback('https://hn.algolia.com/api/v1/search?query=' + query, [query]);

There are no benefits from doing this because useMemo and useCallback are intended for lazy evaluation, while this results in eager evaluation.

The example with getFetchUrl callback isn't very illustrative because memoization doesn't provide any improvements, it could be simplified to:

function SearchResults() {
  const [query, setQuery] = useState('react');

  const fetchUrl = 'https://hn.algolia.com/api/v1/search?query=' + query;

  useEffect(() => {
    // ... Fetch data and do something ...
  }, [fetchUrl]);
}

As for Greeting example, useCallback is redundant. If the calculation is really expensive and needs to be lazily evaluated (in this example it isn't), this is what useMemo is for:

function Greeting({ name }) {
  const result = useMemo(() => {
    return `Hello ${name}`;
  }, [name]);

  return <p>{result}</p>;
}

Upvotes: 1

Related Questions