DEV Community

Kenneth Lum
Kenneth Lum

Posted on

Caching a function instead of something "expensive" using useCallback() in React

We have seen that we can cache something that is "expensive", using useMemo(), in https://dev.to/kennethlum/seeing-usememo-speed-up-our-webpage-3h91

Now a function can be quite simple, but why would we want to cache it to? It can be when we pass into a child component or use it else where, and we want to keep it the same value, so that there is no unnecessary re-rendering.

We can see, in

export default function App() {
  const myFooter = useMemo(() => <Footer n={30000} />, []);
  const [count, setCount] = useState(0);

  function handleClick() {
    setCount(count + 1);
  }
Enter fullscreen mode Exit fullscreen mode

The function handleClick is a new function every time App() is called.

We can use useMemo() to cache it too, just like how we cache <Footer />

The code:

Wrong behavior demo: https://codesandbox.io/s/relaxed-newton-5sqmy?file=/src/App.js

  const handleClick = useMemo(
    () => () => {
      setCount(count + 1);
    },
    []
  );
Enter fullscreen mode Exit fullscreen mode

It can only increment the count to 1, but not more. Why is that? The reason is that we cached the function, which is a closure with the scope chain with count equal to 0. Every time, the function sees count as 0, and therefore the setCount(count + 1) is always setCount(0 + 1).

To fix that behavior, we can use:

  const handleClick = useMemo(
    () => () => {
      setCount(c => c + 1);
    },
    []
  );
Enter fullscreen mode Exit fullscreen mode

Demo: https://codesandbox.io/s/nameless-fast-d0fv1?file=/src/App.js

Note that we don't need to use useMemo(), but can use useCallback(). It is essentially the same thing:

  const handleClick = useCallback(() => {
    setCount((c) => c + 1);
  }, []);
Enter fullscreen mode Exit fullscreen mode

Demo: https://codesandbox.io/s/busy-archimedes-vse8f?file=/src/App.js

Note that we don't need to give a function that return a value, but can provide that function we want to cache directly.

Likewise, if we have

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, []);
Enter fullscreen mode Exit fullscreen mode

It is not going to work: https://codesandbox.io/s/distracted-cloud-o93gw?file=/src/App.js

To see that handleClick is the same value (a reference to the same function), we can use a useRef() to double check it. We can skip this part if useRef() is not familiar to you yet:

  const checkingIt = useRef(null);

  const handleClick = useCallback(() => {
    setCount((c) => c + 1);
  }, []);

  console.log(checkingIt.current === handleClick);
  checkingIt.current = handleClick;
Enter fullscreen mode Exit fullscreen mode

Demo: https://codesandbox.io/s/unruffled-sunset-81vwx?file=/src/App.js

We can see that the first time, the console.log() would print out false, but once we set it, the next time App() is called, it has the same value as the previous time, and would print out true.

If we change it to a new function every time, then it would print out false every time.

Demo: https://codesandbox.io/s/affectionate-dewdney-556mn?file=/src/App.js

Top comments (0)