Why use useMemo and not useCallback here?
Asked Answered
B

3

5

So as i understand the difference between the two is that useCallback is used if a function or object or array is returned while useMemo when a primitive is returned. But i was looking up debouncing (this is the article: https://dmitripavlutin.com/react-throttle-debounce/ and it said useMemo would be a better solution. With useCallback

import { useState, useCallback } from 'react';
import debounce from 'lodash.debounce';
export function FilterList({ names }) {
  const [query, setQuery] = useState("");
  let filteredNames = names;
  if (query !== "") {
    filteredNames = names.filter((name) => {
      return name.toLowerCase().includes(query.toLowerCase());
    });
  }
  const changeHandler = event => {
    setQuery(event.target.value);
  };
  const debouncedChangeHandler = useCallback(
    debounce(changeHandler, 300)
  , []);
  return (
    <div>
      <input 
        onChange={debouncedChangeHandler} 
        type="text" 
        placeholder="Type a query..."
      />
      {filteredNames.map(name => <div key={name}>{name}</div>)}
    </div>
  );
}

With useMemo

import { useState, useMemo } from 'react';
import debounce from 'lodash.debounce';
export function FilterList({ names }) {
  const [query, setQuery] = useState("");
  let filteredNames = names;
  if (query !== "") {
    filteredNames = names.filter((name) => {
      return name.toLowerCase().includes(query.toLowerCase());
    });
  }
  const changeHandler = (event) => {
    setQuery(event.target.value);
  };
  const debouncedChangeHandler = useMemo(
    () => debounce(changeHandler, 300)
  , []);
  return (
    <div>
      <input
        onChange={debouncedChangeHandler}
        type="text"
        placeholder="Type a query..."
      />
      {filteredNames.map(name => <div key={name}>{name}</div>)}
    </div>
  );
}

And i don't understand. Is debounce returning a primitive value? If not how can we use useMemo? Also how is useMemo better than useCallback here?

Brummell answered 20/1, 2022 at 16:37 Comment(1)
useCallback takes a function, and returns that function memoized. useMemo takes a function that returns a value, runs that function, and memoizes that return value.Vasectomy
M
12

First about your quote:

useCallback is used if a function or object or array is returned while useMemo when a primitive is returned

No, this is wrong. useCallback is mainly for memoizing functions. useMemo helps to avoid expensive calculations.


Now for the article. That article prefers useMemo for a different reason, that of performance; however I doubt that in practice this would often result in noticeable performance issues.

 const debouncedChangeHandler = useCallback(
    debounce(changeHandler, 300)
  , []);

It says:

However... this implementation has a small performance issue: each time the component re-renders, a new instance of the debounced function is created by the debounce(changeHandler, 300).

It is saying that even though the debouncedChangeHandler remains the same across re renders due to useCallback, the debounce(changeHandler, 300) is still executed on each render. However apparently calling debounce(changeHandler, 300) on each render is not a problem from correctness point of view because react uses the cached version anyway (unless dependencies change). This is mentioned also in the article:

That's not a problem regarding the correctness: useCallback() makes sure to return the same debounced function instance. But it would be wise to avoid calling debounce(...) on each rendering.

But with useMemo:

  const debouncedChangeHandler = useMemo(
    () => debounce(changeHandler, 300)
  , []);

it claims:

useMemo(() => debounce(changeHandler, 300), []) memoizes the debounced handler, but also calls debounce() only during initial rendering of the component.

Because in this example useMemo unlike useCallback doesn't directly call debounce, rather calls it inside an inline function.


Run this code:

let fakeDebounce = (param) => {
  console.log(param);
  return () => {};
};

export default function App() {
  let f = React.useCallback(fakeDebounce('test1'), []);
  let g = React.useMemo(() => fakeDebounce('test2'), []);
  let [x, setX] = React.useState(0);
  return (
    <div
      onClick={() => {
        setX(x + 1);
      }}
    >
      <h1>{x}</h1>
      <p>Start editing to see some magic happen :)</p>
    </div>
  );
}

Click div anywhere and see how test2 isn't logged anymore.

Measurable answered 20/1, 2022 at 16:49 Comment(5)
Thanks but i am even more confused. I took a course in which they said this is the difference between useMemo and useCallback. Can you tell when should useMemo and useCallback be used?Brummell
@Brummell Check docs: Hard to explain as comment I suggest look for info online, there should be plenty. reactjs.org/docs/hooks-reference.html#usecallbackMeasurable
@giorgimoniava, Can we do something like this useCallback( (e) => debounce((e) => changeHandler(e), 300) , []); instead of calling the debounce inside useCallback every render?Metcalfe
@Metcalfe that would return a different result as compared to useCallback( debounce(changeHandler, 300),[]). It would return a function, which you would additionally have to invoke on each render; and I am not sure that way you would benefit from using debounce.Measurable
@giorgimoniava ok, got it.Metcalfe
D
2
  const debouncedChangeHandler = useCallback(
    debounce(changeHandler, 300)
  , []);

in every render:

  1. debounce(changeHandler, 300) will run(it is not a function, it is a called function) and resolve into a value(which is a callback)
  2. then useCallback will run, check the the dependency to determine whether it should return memoized value(callback) or new value, so in your case, it will return memoized value
  const debouncedChangeHandler = useMemo(
    () => debounce(changeHandler, 300)
  , [])

in every render

  1. () => debounce(changeHandler, 300) will not run, it is value(callback)
  2. then useMemo will run, it will check the dependency to determine whether to run the callback or not, in your case, it will not run the callback

hence useMemo are more efficient

Dexter answered 20/1, 2022 at 17:2 Comment(0)
L
0

if you want to return a value, use useMemo if you use useMemo, it will cause side effect, which makes code look bad. if you want to return a callback,use useCallback

Lemar answered 22/10, 2022 at 14:1 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.