React useCallback and JS function closure 🤔
Tags: javascript, react, webdev, programming
Published: 2023-02-21 12:07:12

---

As many of you know that `useCallback` is a ***built-in hook in React***, usually used to scale and optimize large applications rendering performance by memoizing the functions to reduce recreating them in each re-render occurs. But you may not know how this hook actually works and how that is related to function closures, so this is what I'm gonna explain here!
<br/>

> ⚠️ If you don't know why `useCallback` is used for, I recommend you to go through [useCallback in React docs](https://beta.reactjs.org/reference/react/useCallback) before you keep going in this blog.

<br/>
`useCallback` takes two parameters, a callback function and
an array of dependencies. In the first render `useCallback` returns the passed function, and for subsequent renders it compares the old dependencies with the passed one using [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) algorithm, If nothing has been changed in the dependencies will return the same old function, otherwise will return the passed function.

🔻See this simple skeleton for `useCallback`🔻
```
const memoizedCallback = useCallback(
  () => {
    // function logic goes here
  },
  [dependencyArray]
);
```

🔻And this is a simulation for `useCallback` as a normal JS function🔻
```
/*
    Please note that this code is theoretical, this is not 
    how we actually compare two arrays together.
 */

let oldDependancies, oldCallBack; //React stores these somewhere

function useCallback(callback, dependencies) {
  if(oldDependancies === dependencies) return oldCallBack;

  oldDependancies = dependencies;
  oldCallBack = callback;

  return callback;
}
```

***Theoretically*** the above code compares the old dependencies with the passed one, if the old one is the same as the passed one then will return the old callback function, otherwise it will assign the new dependencies array and the new callback to the old one, and returns the passed function.


Now lets dive into with a real example, the great **_Counter component_** 🤯

```
import React, { useState, useCallback } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  const incrementCount = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  const decrementCount = useCallback(() => {
    setCount(count - 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment</button>
      <button onClick={decrementCount}>Decrement</button>
    </div>
  );
}
```

This component renders a count number with increment and decrement buttons. Each of increment and decrement handlers memoized (cached) using `useCallback`, and it's working as expected when click the buttons will increase and decrease the count by `1`. You can interact with it using the below box!

{% codesandbox hsgq1z %}

## Problem

Let's change the above code a bit by removing the `count` from the dependencies array in each `useCallback` for `incrementCount` and `decrementCount`, to be like this.

```
  const incrementCount = useCallback(() => {
    setCount(count + 1);
  }, []);

  const decrementCount = useCallback(() => {
    setCount(count - 1);
  }, []);
```

Can you guess now what are the results when click `increment` and `decrement` buttons multiple times? check your answer using the below box

{% codesandbox sd5tdg %}

Now after you've clicked many times on the buttons, apparently the count won't increase more than 1 or decrease less than -1, and the reason behind that is the function [closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#closure). 

Using `useCallback` with an empty dependency array tills react to create the `incrementCount` and `decrementCount` functions only once, and not to recreate them on every render. Therefore, the closure of these functions contains the initial (stale) value of count, which is `0`.

When the `incrementCount` function is called, it sets the value of `count` to `count + 1`, but count is still the old value of `0` in this closure, so the result is `1`. On subsequent calls to `incrementCount`, the closure still contains the old value of `count`, which is `0`, so the result is still `1`. And the same for `decrementCount` the result will allways be `-1`.

## Solution

The solution for this issue is to add each variable affects the result to the dependency array, **or** by passing an [updater function](https://beta.reactjs.org/reference/react/useState#updating-state-based-on-the-previous-state) to `setCount` instead of `count +- 1`.

So `incrementCount` will be like this 

```
const incrementCount = useCallback(() => {
    setCount(count + 1);
  }, [count]);
```
OR 

```
const incrementCount = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);
```
The reason is that updater function works with an empty dependency array, is this updater function is called during the rendering process and reads the actual value of the state at that time, to know more check [queueing a series of state updates](https://beta.reactjs.org/learn/queueing-a-series-of-state-updates).

## Conclusion

- Each variable affects the result of the function passed to `useCallback` should be added to the dependency array.
- If the state change depends on the old state it's better pass an updater function to the state setter function.
- The memoized callback will has the same old closure until re-creating it because of dependency changes.

<br/>
Thanks for reading!

Let me know if you have any thoughts by commenting below.