skip to Main Content

I’m learning about React hooks and I have a question about the useCallback hook.

From what I understand, useCallback is useful for creating functions that you want to cache and reuse when the component re-renders (as opposed to creating the function again on every re-render). This is useful when you want to pass the function to a child component as a prop without causing the child component to re-render every time the parent re-renders.

If that’s correct, then here’s my question:

What happens in this scenario:

const handlerFunction = useCallback(() => console.log('I am memoized'), []);

const handlerFunction2 = useCallback(() => console.log('I am memoized'), []);

In the second call to useCallback(), will it recognize the function as the same as that passed to the first call and return the memoized function?

If not, how would it compare the function passed to useCallback() to the memoized function in the cache upon re-rendering?

2

Answers


  1. In the second call to useCallback(), will it recognize the function as the same as that passed to the first call and return the memoized function?

    No.

    If not, how would it compare the function passed to useCallback() to the memoized function in the cache upon re-rendering?

    The functions are never compared. Each useCallback is independent, and is just checking whether it’s dependency array has changed compared to the last render.

    As for how it implements this, hooks are designed with the assumption that you will always call the same number of hooks in the same order. This is referred to as the rules of hooks. Because react can assume you are following the rules of hooks, it just needs to keep track of the order.

    React keeps an internal array to track the data needed for hooks. Every time your component renders, react sets an internal index to 0. You call useCallback the first time, and react saves whatever data it needs in index 0. Then you call useCallback again, and the necessary data is saved in index 1. The next time the component renders, the index starts counting from 0 again. Your first call to useCallback interacts with the data in index 0, returning the saved function if appropriate. Then your second call to useCallback interacts with the data in index 1.

    Login or Signup to reply.
  2. Reactive values and Dependency arrays are the two key constructs working under the hood.

    As we know, Reactive values of a component include – the props, state and the local variables computed on props or state. You would be able to know more on this from here : All variables declared in the component body are reactive

    The Dependency array or list is the mechanism which makes it possible to find the changes in Reactive values between the two renders – current and the previous. More can be read from here : Specify the Effect dependencies

    By the way, useCallback may be better understood if we discuss the same under the background of the very familiar hook useEffect. As we know, this hook acts as a gateway between a React component and a non-React system. The reason for bringing useEffect over here for our discussion is that, just like useCallback, useEffect also has “the need to check and decide” if there is something to do with the code passed into it. For this requirement, useEffect also depends upon the Reactive values and its dependency array. These two examples may prompt us to draw the underlying parameters of useCallback and useEffect hooks – Both work on the Reactive values and dependency array or in short the “dependency array of Reactive values”.

    Please do note that while useEffect runs its handler just after every render if and only if the dependencies have been changed between the renders, useCallback returns the function definition whether a new definition or the previous one itself, again depending upon the status of dependency array. Although functionally both are different, the parameters under which they operate are the same – The Reactive values and the dependency arrays.

    Now coming to the specific question:

    Please see below a sample code which is based on the code snippet of your question.

    Please take note of the important steps in it :

    Step 1 : Initial render

    a) App and Child components will render.

    b) During the initial render, the variables handlerFunction and handlerFunction2 will be assigned with the given function definitions.

    c) These variables are passed into the component Child as its props with the same values in the initial render.

    Step 2 : While clicking the button

    a) This will trigger a state change in the component App.

    b) However, during the rerender, the variables handlerFunction and handlerFunction2 will remain unchanged.

    c) This is due to the fact that the dependency array is empty ([ ]), it has no reactive values, and therefore there is no change which necessitates to redefine the functional values in the two variables. This is the most important aspect to take note of.

    d) As the functional values in the two variables remain unchanged, the props passed into the component Child will also remain unchanged.

    e) Since Child has been already enclosed in memo function, it will keep skipping rendering Child as long as the props remain unchanged.

    f) Therefore the Child will never re-render, this can be verified by ensuring that the statement console.log(‘Child rendered’) is not getting executed during render.

    App.js

    import { useCallback, useEffect, useState, memo } from 'react';
    
    export default function App() {
      const [someState, setSomeState] = useState('');
    
      const handlerFunction = useCallback(() => console.log('I am memoized'), []);
    
      const handlerFunction2 = useCallback(() => console.log('I am memoized'), []);
    
      return (
        <>
          <Child
            onSomeButtonClick={handlerFunction}
            onSomeAnotherButtonClick={handlerFunction2}
          />
          <hr />
          <button
            onClick={(e) => {
              setSomeState(Math.random());
            }}
          >
            Force App component to Render, causing Child to render normally
          </button>
        </>
      );
    }
    
    const Child = memo(function Child({
      onSomeButtonClick,
      onSomeAnotherButtonClick,
    }) {
      console.log('Child rendered');
      return <>Child rendered</>;
    });
    

    Test run

    a) On load of the App
    Browser display - on load of the App

    b) On Button click, there is nothing logged in the Console from Child component

    Browser display - On Button click

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search