Reac.memo , useMemo() and useCallback() explained

In this article, you will understand the actually use case of React.memo() , useMemo() and useCallback()

UseMemo()

  • The value returned is stored in a cache, so that next time you can directly retrieve it from the cache.
  • The useMemo Hook can be used to keep expensive, resource-intensive functions from needlessly running.
  • The useMemo Hook only runs when one of its dependencies is updated, hence performance increases.
import { useState, useMemo } from "react";

const UseStateHook = () => {
  const [counterOne, setCounterOne] = useState(0);
  const [counterTwo, setCounterTwo] = useState(0);

  const incrementOne = () => {
    console.log("one");
    setCounterOne(counterOne + 1);
  };

  //the expensive function is kept inside useMemo , and it onyl work when "counterOne" value changes
  const isEven = useMemo(() => {
    let i = 0;
    console.warn("hllo");
    while (i < 2000000000) i++;
    return counterOne % 2 === 0;
  }, [counterOne]);

  const incrementTwo = () => {
    console.log("two");
    setCounterTwo(counterTwo + 1);
  };

 //useMemo - return value 
  //useCallBack - return entire function
  console.log(isEven);

  //this is expesive fucntion which is making the entire DOM rendering slow
  // function isEven() {
  //   let i = 0;
  //   console.warn("hllo");
  //   while (i < 2000000000) i++;
  //   return counterOne % 2 === 0;
  // }

  return (
    <>
      <button onClick={incrementOne}>Counter One - {counterOne}</button>
      //<p> {isEven() ? "even" : "odd"}</p>
      <p> {isEven ? "even" : "odd"}</p>

      <button onClick={incrementTwo}>Counter Two - {counterTwo}</button>
    </>
  );
};

export default UseStateHook;

In the above example –

  • isEven() was an expensive function, hence it was affecting the entire DOM performance even though is not related to the other logic.
  • So isEven is wrapped inside useMemo() and given a condition that this will only run when “counterOne” value changes,
  • Now Counter Two button performance is not affected, hence entire dom performance increased

useCallBack()

You can copy the above function and replace “useMemo” with “useCallback” , and the function will work fine.

The main difference is that useMemo returns a memoized value and useCallback returns a memoized function.

But for your better understanding, we will be dealing with a new example for useCallback .

Before writing code , you will notice we will also use React.memo() with useCallback , so let’s quickly understand this first.

React.memo()

memo() usually stops the re-rendring of the component if the props or state have not changed of that particular component

Some key points –

  • Improves performance as it restricts unnecessary re-rendering of the component.
  • Memoize(cache) a component hence faster reload

How to write memo()

To use memo , We usually wrap the export component within memo function

import React from "react";

const Title = () => {
  console.log("title component render");
  return <div>Title</div>;
};

export default React.memo(Title);

Now lets understand useCallback() with a code

Create 3 components –

Step 1 – Create 3 Compponent

  • ParentComponent.jsx
  • Button.jsx
  • Count.jsx

Here the <ParentComponent /> will wrap the <Button/> and <Count/> Component component

Needless to say, the main <App /> component will be wrapping the <Parent/> component

Demo –

ParentComponent.jsx 
------------------
<ParentComponent>
   <Count/>
   <Button/>
<ParentComponnt>


App.jsx
---------

<App>
<ParentComponent/>
</App>
Now that you understood the heirarchy , let write some code

ParentComponent.jsx –

import { useState } from "react";
import Count from "./Count";

import Button from "./Button";

const ParentComponent = () => {
  const [age, setAge] = useState(25);
  const [salary, setSalary] = useState(5000);

  const incrementAge = () => {
    setAge(age + 1);
  };

  const incrementSalary = () => {
    setSalary(salary + 1000);
  };

  return (
    <>
      <Count text="age" count={age} />
      <Button handleClick={incrementAge} count={age}>
        Increase age
      </Button>
      <Count text="salary" count={salary} />
      <Button handleClick={incrementSalary}>Increase Salary</Button>
    </>
  );
};

export default ParentComponent;

Button.jsx –

import React from "react";

const Button = ({ handleClick, children }) => {
  console.log("button component render ", children);
  return (
    <div>
      <button onClick={handleClick}>{children}</button>
    </div>
  );
};

export default Button;

Count.jsx –

import React from "react";

const Count = (props) => {
  console.log("count component render", props.text);
  return (
    <div>
      {props.text} - {props.count}
    </div>
  );
};

export default Count;

Let,s see the output of the above code –

On first render , all the 4 elements will be printed , as we have consoled all the elements

Why 4 times  ?
-----------
<Count/> //for age
<Button>Increase Age</Button>
<Count/> //for salary
<Button>Increase Salary</Button>

Now if I click only on “Increase Age” button , you will see all the 4 elements again got re-rendered.

Similar thing happens , if I click on “Increase Salary” button

Well! to prevent this we can use React.memo() , which help in stopping the render of those component, whose props have not changed.

So let’s add that.

Button.jsx

import React from "react";

const Button = ({ handleClick, children }) => {
  console.log("button component render ", children);
  return (
    <div>
      <button onClick={handleClick}>{children}</button>
    </div>
  );
};

export default React.memo(Button);

Count.jsx

import React from "react";

const Count = (props) => {
  console.log("count component render", props.text);
  return (
    <div>
      {props.text} - {props.count}
    </div>
  );
};

export default React.memo(Count);

We don’t need to add React.memo() in the parent component.

Now lets see the output –

Wee now you can see, as we click on increase age” button ,the <Count /> component render only once i.e only for age. This is what we wanted, But you can see the button for increase salary also render , which is not as expected.

Now lets add useCallback() to our functions

ParentComponent.jsx

import { useState, useCallback } from "react";
import Count from "./Count";

import Button from "./Button";

const ParentComponent = () => {
  const [age, setAge] = useState(25);
  const [salary, setSalary] = useState(5000);

  const incrementAge = useCallback(() => {
    setAge(age + 1);
  }, [age]);

  const incrementSalary = useCallback(() => {
    setSalary(salary + 1000);
  }, [salary]);

  console.log("reference", incrementAge === incrementSalary); //false

  return (
    <>
      <Count text="age" count={age} />
      <Button handleClick={incrementAge} count={age}>
        Increase age
      </Button>
      <Count text="salary" count={salary} />
      <Button handleClick={incrementSalary}>Increase Salary</Button>
    </>
  );
};

export default ParentComponent;

Noe finally take a deeper breath and lets now see the output,

And hurray!!! We finally got the right output. Only the component related to increase age button got rendered.

We have successfully restricted our component for un-necessary re-render and increased the performance of our website.

Note - It is not advisable to apply useCallback() all the time, so always discuss with your team and then apply. Read this

Another example to understand useCallback()

Let understand the issue in the below code –

The “handleText” function in the App.jsx has no relation with the users object , still whenever we change the input data , the setText state updated and hence all the child (List component in this case) will be re-rendered.

Nothing to worry here because this is how react works. Whenever we change a parent state , the child component also re-render.

This is generally fixed by React.memo()

Note : React.memo() is not linked with useMemo().

List.jsx

import React from "react";

const List = ({ users, onRemove }) => {
  console.log("rendering list");
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          {user.name} <span onClick={() => onRemove(user.id)}>X</span>
        </li>
      ))}
    </ul>
  );
};

export default React.memo(List);

App.jsx

import { useCallback, useState } from "react";
import List from "./components/List";

const initialUsers = [
  {
    id: "1",
    name: "foo",
  },
  {
    id: "2",
    name: "bar",
  },
  {
    id: "3",
    name: "loo",
  },
];

const App = () => {
  const [users, setUsers] = useState(initialUsers);
  const [text, setText] = useState("");

  const handleRemove = (userId) => {
    console.log("handleRemove", userId);
    const filteredUsers = users.filter((user) => user.id !== userId);
    setUsers(filteredUsers);
  };

  //this function has no relation with the users object
  const handleText = (event) => {
    setText(event.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={handleText} />
      <List users={users} onRemove={handleRemove} />
    </div>
  );
};

export default App;

But hope you have seen till now, the issue is still not fixed.

The fact that here React.memo() also failed is because memo() always compares the earlier state change with the new one, if anything is changes , it will re-render.

And here since every time a new inline function is created , everytime React.memo() finds a new function and hence, it initiated a new re-render.

Now to fix this we need to tell react to cache the function ,or basically keep the same reference of the function every time it is called, and that’s when useCallback() comes into picture.

Just wrap the handleRemove() function with callback

 const handleRemove = useCallback(
    (userId) => {
      console.log("handleRemove", userId);
      const filteredUsers = users.filter((user) => user.id !== userId);
      setUsers(filteredUsers);
    },
    [users]
  );

Conclusion

React.memo is a higher-order component (HOC) that prevents a functional component from re-rendering if its props haven’t changed.

useMemo memoizes the result of a function, preventing it from being recalculated on every render unless its dependencies change.

useCallback memoizes a function, preventing it from being recreated on every render unless its dependencies change.

Help Others

Leave a Reply

Your email address will not be published. Required fields are marked *