DEV Community

Cover image for Difference between useCallback(),useMemo() and React.memo()
Vignesh Pugazhendhi
Vignesh Pugazhendhi

Posted on

Difference between useCallback(),useMemo() and React.memo()

Reconciliation or selective re-rendering is a major boost in the performance optimization of a React application. By selective re-rendering, I mean only those sub-components are re-rendered which witness a change in it's state or props passed to it. This is taken care by the React's virtual DOM, which computes the differences between the DOM and updates the UI efficiently. A component is re-rendered if and only if either of the props passed or it's local state is changed. And when a component is re-rendered, it's child components are re-rendered.

React.memo

React.memo was introduced in functional components in react v16.6. React.memo is a convenient way to avoid re-rendering in functional components. All you have to do is to wrap the functional component with React.memo() HOC(Higher Order Component). It can be used for both class based and functional components. It compares the props passed and the local state between any two consecutive re-renders and can bail out a component from re-rendering if there is no change in the props and the state. Go through the following code snippet to understand the memoization of components:

import React,{useState} from 'react';
import ComponentB from './ComponentB';
import Trainer from './Trainer';

function ComponentA() {
    const [pokemons,setPokemons]=useState(
    ["bulbasaur","charmendar","pikachu"]
);
    const [trainer,setTrainer]=useState('');

    console.log('componentA rendered');

    return (
        <div>
            <Trainer handleTrainer={setTrainer}/>
            <ComponentB pokemons={pokemons}/>
            <span>{trainer}</span>
        </div>
    )
}

export default ComponentA
Enter fullscreen mode Exit fullscreen mode
import React from 'react';
import styled from 'styled-components';

function Trainer({handleTrainer}) {

    console.log('Trainer rendered');

    return (
        <div>
            <Input onChange={(e)=>handleTrainer(e.target.value)}/>
        </div>
    )
}

const Input=styled
.input
.attrs((props)=>({type:'text',placeholder:'Trainer'}))`
border:2px solid black;
margin:auto;
margin-bottom:20px !important;
margin-top:10px !important;
max-width:50%;
`;

export default Trainer;
Enter fullscreen mode Exit fullscreen mode
import React from 'react';
import styled from 'styled-components';

function ComponentB({pokemons}) {

    console.log('ComponentB rendered');

    return (
        <React.Fragment>
            <UnorderedList>
                {pokemons.map((pokemon,index)=>{
                    return <ListItem key={index}>{pokemon}</ListItem>
                })}
            </UnorderedList>
        </React.Fragment>
    )
}

const UnorderedList=styled.ul`
list-style-type:none;
`;
const ListItem=styled.li`
border:1px solid transparent;
margin:auto;
margin-top:20px !important;
width:50%;
background:#00e6ac;
color:white;
text-transform:capitalize;
`;

export default ComponentB;


Enter fullscreen mode Exit fullscreen mode

Code breakdown

ComponentA is the parent component of ComponentB and Trainer. Local state pokemons is passed as a prop to ComponentB and setTrainer function is passed as a prop to Trainer. When the application loads for the first time, the parent component along with it's children are rendered. You can console.log and check the number of rendering in your browser's developer tools. For every state update of the ComponentA, both ComponentB and Trainer are re-rendered,which we do not want to. To avoid unnecessary renderings, wrap both the children components with React.memo() HOC. Wrapping the components with React.memo() compares the props between two consecutive renders and bails out unnecessary re-renders of it's children components. Though it can memoize the components and boost the performance of the application to a certain level, there is always a catch. A good use case where this can be used is when there are atleast 10 nested components with complex computations. Comparison of props for 2-3 components can be little costly in terms of memoization.

UseCallback
Suppose we pass a callback function clearTrainer() to ComponentB through props. The purpose of this function is to clear the trainer state to empty string. Now,run the application and you should see 'componentB rendering' in your developer console. This turns out to be unexpected as ComponentB is wrapped with React.memo(). For this to be understood,we need to understand function equality in javascript. Every function in js is an object. For two objects to be equal,it's necessary for both of them to be of same definition and share same location in memory. Now every time ComponentA is re-rendered,a new instance of clearTrainer() function is created. So for subsequent re-renders,we are passing two different instances of the same function definition and thus ComponentB also re-renders. To solve this,we need to wrap the callback function with useCallback() hook. Now useCallback takes two arguments-one is the callback function and second is an array of dependencies for which a new instance of the callback function is to be created. ComponentB's UI depends upon the prop pokemons. So,pass pokemons as a dependency.

function ComponentA() {
    const [pokemons,setPokemons]=useState(
    ["bulbasaur","charmendar","pikachu"]
);
    const [trainer,setTrainer]=useState('');

    const clearTrainer=useCallback(()=>{
        setTrainer('');
    },[pokemons]);

    return (
        <div>
            <Trainer handleTrainer={setTrainer}/>
            <ComponentB 
            pokemons={pokemons} 
            clearTrainer={clearTrainer}
            />
            <span>{trainer}</span>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

useMemo
useMemo() is similar to useCallback().The only difference between these two hooks is, one caches the function and the other caches any value type. Consider a situation where you have to render a long list of elements and each element calls an expensive function for it to render some information. During the first render, the thread is completely blocked until the expensive functions are executed. In subsequent re-renders useMemo() makes use of the memoized value to avoid calls to those expensive functions. This can be understood with the following code snippet:

export const Items=({list})=>{
  const listContent=list.map(item=>{
    return {
     name:item.name
     price:item.price
     priceWithoutVat:expensiveFunctionOne(item.totalPrice,item.basePrice)
    }
  });
  return <div>{listContent}</div>
}
Enter fullscreen mode Exit fullscreen mode

Let's assume this code snippet is in a component which re-renders frequently for some obvious reasons. Now to avoid the complex computations, we memoize the value of listContent. To do this,wrap it in useMemo() hook. useMemo() takes two arguments. One is the value to be memoized and the other, a list of dependencies for which this value is to be recalculated.

export const Items=React.useMemo(({list})=>{
  const listContent=list.map(item=>{
    return {
     name:item.name
     price:item.price
     priceWithoutVat:expensiveFunctionOne(item.totalPrice,item.basePrice)
    }
  });
  return <div>{listContent}</div>
},[list])
Enter fullscreen mode Exit fullscreen mode

Top comments (0)