Improve Your React Application Performance With useCallback and useMemo Hooks

When you develop a scalable react application the performance issues come into the picture. For some smaller, less complex applications, performance may never become an issue. But for large-scale react apps, slow performance is a big problem.

In most cases, The main reason for slow performance in react application is unnecessary re-renders. I am going to discuss useCallback & useMemo Hooks and how to use them to optimize the performance of the web application with examples.

Why React component is re-render?

When a change happens in a component’s props or state, this will automatically trigger a re-render of that component.

Now let's discuss how useCallback and useMemo are used to prevent unnecessary re-renders.

1) useCallback Hook

The useCallback() hook helps us to memoize the functions so that it prevents the re-creating of functions on every re-render.

The callback function we passed to the useCallback hook is only re-created when one of its dependencies is changed.

Syntax

 const func = useCallback(() => {
    // function definition
}, [dependencies])

Let’s see an example:

Without useCallback Hook:

import React, { useState } from 'react';

let functionCounter = new Set();
const App = () => {
  const [counterOne, setCounterOne] = useState(0);
  const [counterTwo, setCounterTwo] = useState(0);

  const handleCounterOne = () => {
    console.log('Counter one function called');
    setCounterOne(counterOne + 1);
  };
  const handleCounterTwo = () => {
    console.log('Counter two function called');
    setCounterTwo(counterOne + 1);
  };

  functionCounter.add(handleCounterOne);
  functionCounter.add(handleCounterTwo);
  alert(`Total Function created ${functionCounter.size}`);

  return (
    <div>
      <div>
        <h3>function Creation Counter :- {functionCounter.size}</h3>
      </div>
      <button onClick={handleCounterOne}>Counter one value {counterOne}</button>
      <button onClick={handleCounterTwo}>Counter Two value {counterTwo}</button>
    </div>
  );
};

export default App;

In the above code, The problem is that once the counter value is updated, handleCounterOne and handleCounterTwo both functions are recreated again. The alert increases by two at a time but if we update some states all the functions related to that states should only re-instantiated. If another state value is unchanged, it should not be touched right?.

To solve this problem we need to wrap our functions with useCallback hook.

With useCallback hook:

import React, { useCallback, useState } from 'react';

let functionCounter = new Set();
const App = () => {
  const [counterOne, setCounterOne] = useState(0);
  const [counterTwo, setCounterTwo] = useState(0);

  const handleCounterOne = useCallback(() => {
    console.log('Counter one function called');
    setCounterOne(counterOne + 1);
  }, [counterOne]);

  const handleCounterTwo = useCallback(() => {
    console.log('Counter two function called');
    setCounterTwo(counterOne + 1);
  }, [counterTwo]);

  functionCounter.add(handleCounterOne);
  functionCounter.add(handleCounterTwo);
  alert(`Total Function created ${functionCounter.size}`);

  return (
    <div>
      <div>
        <h3>function Creation Counter :- {functionCounter.size}</h3>
      </div>
      <button onClick={handleCounterOne}>Counter one value {counterOne}</button>
      <button onClick={handleCounterTwo}>Counter Two value {counterTwo}</button>
    </div>
  );
};

export default App;

Here wrapped our two functions with useCallback hook and second argument is a dependency, so that functions are only re-created if one of its dependency value is changed.

In the above code when we change the state ‘countOne’ then only one function will re-instantiated so the set size will increase by 1.

2) useMemo Hook

The useMemo() hook helps us to memoize the function result so that it prevents the re-computation of functions on every re-render

A memoized function remembers the results of output for a given set of inputs. For example, if there is a function to add two numbers, and we give the parameter as 1 and 2 for the first time the function will add these two numbers and return 3, but if the same inputs come again then we will return the cached value.

Syntax

 const func = useMemo(() => {
    // function definition
}, [dependencies])

useMemo takes one function and the array of arguments. useMemo runs this function only when there is a change in these arguments. If we pass the empty array then it will run once and if we pass not an empty array then it will run every time the re-render happens.

Let’s see an example:

Without useMemo Hook:

import React, { useState } from 'react';
const App = () => {
  const [name, setName] = useState('Shubham');
  const printer = () => {
    // cpu-intensive task
    console.log('printer function called');
    let i = 0;
    while (i < 3000000000) i++;
    return "It's slow down input field";
  };

  return (
    <div>
      <input
        placeholder='name'
        value={name}
        onChange={(e) => setName(e.target.value)}
      />

      <h3>{printer()}</h3>
    </div>
  );
};

export default App;

In the above example, even when the user types a name, the printer function is also called on every re-render which makes the user input field(name input) very slow because it runs a CPU-intensive function(In our case printer) in every re-render.

To solve this problem we need to use useMemo hook.

With useMemo hook:

import React, { useMemo, useState } from 'react';
const App = () => {
  const [name, setName] = useState('Shubham');

  // useMemo cached this function value
  const printer = useMemo(() => {
    // not re-compute result on every re-render
    console.log('printer function called');
    let i = 0;
    while (i < 3000000000) i++;
    return 'Issue is resolved by useMemo hooks ';
  }, []);
  return (
    <div>
      <input
        placeholder='name'
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <h3>{printer}</h3>
    </div>
  );
};

export default App;

In the above code printer() function is only computed once and its result is cached.

Thanks for reading!