React Optimization: React.memo, useCallback, useMemo

React Optimization: React.memo, useCallback, useMemo

Why do we need to optimize React components?

It is needed because of how React Reconciliation updates the virtual DOM. React uses BFS algorithm to check if a node needs to be replaced in virtual DOM, where each node is an element. Now the diffing algorithm does not check whether the child nodes of an element that it is going to replace are updated. They just replace them, causing an unnecessary re-render of children elements.

Let me explain with an example,

<ParentComponent>
    <ChildComponent {...props}/>
<ParentComponent>
<AnotherComponent/>

In the above code if the 'ParentComponent' is re-rendered the 'ChildComponent' will also be re-rendered. The 'AnotherComponent' will not be re-rendered because of the 'ParentComponent'.

If the 'ChildComponent' is a render heavy component and causes the web app to load slowly then there is a way in React to make it render only if the 'ChildComponent' itself is updated.

React.memo

//Inside ChildComponent File export the component as below
export default React.memo(ChildComponent);

If your component renders the same result given the same props, you can wrap it in a call to 'React.memo', React will skip rendering the component, and reuse the last rendered result. So the re-render of the Parent component will not cause unnecessary re-render of the child component.

Keep a note that React.memo only does a shallow comparison of the props i.e. it will be able to compare primitive props like strings and numbers by value but when it comes to comparing objects and functions it will only compare the references to it. You can add an optional function argument to do a custom comparison based on your needs.

function ChildComponent(props) {
  /* render using props */
}
function isEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
export default React.memo(ChildComponent, isEqual);

Now comes the need for another function(a hook) that helps with the reference equality of a function.

useCallback

'useCallback' is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders. Here optimized child components mean components wrapped by 'React.memo'

'useCallback' takes 2 arguments, the first one is the function to be memoized and the second argument is the dependency array.

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

'useCallback' will return a memoized version of the callback that only changes if one of the dependencies has changed.

You can pass the memoized callback to the dependency array of useEffect just as you would pass any other prop to it.

If you don't already know, you might be having this question: 'What the hell does memoize mean?' Scroll below to find out.

Memoization

Memoization is an optimization technique that speeds up applications by storing the results of expensive function calls and returning the cached result when the same inputs are supplied again.

By expensive function call, I mean a function call that consumes huge chunks of time and memory during execution due to heavy computation.

By cache, I mean a temporary data store that holds data so that future requests for that data can be served faster. This data store can be a simple data structure like an array where we can store values.

Now how do we memoize a function in React? use memo!!

useMemo

'useMemo' will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

const memoizedValue = useMemo(() => expensiveFunction(a, b), [a, b]);

'useMemo' takes 2 arguments the first one is the function and the second argument is the dependency array. If no array is provided, a new value will be computed on every render.

I hope that the difference between useMemo and useCallback is clear. useCallback returns a memoized callback whereas useMemo returns a memoized value.