Use Memoization in React with React Memo and useCallback
By: Chamith Madusanka
Tags: Performance, React Hook, Reactjs, Memoization, React

Memoization is speed optimization technique in programing, where a given function, you return a cached version of the output if the same inputs are used.

How re-render works in React ?

When the Virtual DOM gets updated, React compares it to to a previous snapshot of the Virtual DOM and then only updates what has changed in the real DOM. If nothing changed, the real DOM won’t be updated at all.

When does React re-render components?

When a state changes. When a receiving prop changes. When its parent component re-render by default child will re-render.

Let’s do some coding…

In this example you can see when a we click on the button, state of parent changes. Then for that each change the child component renders. This is the normal behavior of React. It doesn’t matter for small components like these. But when it comes to bigger components with external API calls it will be a big performance hit for your application. That is where we can use Memoization in React.

What is memoization ?

Memoization is speed optimization technique in programing, where a given function, you return a cached version of the output if the same inputs are used. For a given input memoized function always returns same output.

In React input to a memoized component is props. It can be a function or a value. When memoizing components memoized component does shallow comparison of the props. If it sees any change in props it will re-render.

We can achieve memoization in React using React.memo or Pure Components.

Memoize using React.memo

When a component is wrapped in React.memo(), React renders the component and memoizes the result. Before the next render, if the new props are the same, React reuses the memoized result skipping the next rendering.

Let’s see the memoization in action. We will modify the component in our previous example. The functional component Child is wrapped in React.memo():

const Child = () => { console.log("Child component is rendering!!!") return (

<div>

Child component

</div>

)

} export default memo(Child)

Now you can see child component is not rendering even though we change the state in parent.

Now let’s pass count as a prop to our Child component.

const Child = (props) => { const { count } = props console.log("Child component is rendering!!!") return (

<div>

Child component {count}

</div>

)

} export default memo(Child)

Now you can see child component is re-rendering for parent state changes because we are passing count as a prop to child component. Component does shallow comparison of the props. It sees the change in the props and re-render.

When using memo there is a special scenario we need to be aware of. Suppose we are passing a callback function to our child component. I will modify the code to demonstrate this scenario.

const App = () => { const [countParent, setParentCount] = useState(0)

const [countChild, setChildCount] = useState(0) const increaseCount = () => {

setChildCount(countChild + 1)

} return (

<div className="App">

<div className="main">

<button type="button" onClick={() => { setParentCount(countParent + 1) }}>Parent button</button>

<p>{countParent}</p>

<Child count={countChild} onIncreaseCount={increaseCount} />

</div>

</div>

);

} export default App; const Child = (props) => { const { count, onIncreaseCount } = props console.log("Child component is rendering!!!") return (

<div>

Child component {count}

<button type="button" onClick={onIncreaseCount}>Child button</button>

</div>

)

} export default memo(Child)

Now we click the parent button you can see child component renders even though we didn’t change props related to the child component. Reason for this is when we change the state of the parent component it is rendering again. For each render it will create a new increaseCount function. We are passing this function to our child component. Even though the function is same memoized component detects it as prop change and render again. To cater this problem we can use useCallback hook in react.

const App = () => { const [countParent, setParentCount] = useState(0)

const [countChild, setChildCount] = useState(0) const increaseCount = useCallback(() => {

setChildCount(countChild + 1)

}, [countChild]) return (

<div className="App">

<div className="main">

<button type="button" onClick={() => { setParentCount(countParent + 1) }}>Parent button</button>

<p>{countParent}</p>

<Child count={countChild} onIncreaseCount={increaseCount} />

</div>

</div>

);

} export default App;

In the [] of the useCallback function we can define for which state value changes that function needs to be created again.

With this change when we click on parent button it only change parent count value. It doesn’t render child component again.

Child component only render when we click on the child button.

It is very important to use useCallback hook with React.memo to get good performance.

By default memoized component do a shallow comparison. But if we want we can add a custom comparison to the memoized component.

const Child = (props) => { const { count, onIncreaseCount } = props console.log("Child component is rendering!!!") return (

<div>

Child component {count}

<button type="button" onClick={onIncreaseCount}>Child button</button>

</div>

)

} const areEqual = (pevState, nextState) => {

return pevState.count === nextState.count

} export default memo(Child, areEqual)



Conclusion:

React.memo() is a great tool to memoize functional components. When applied correctly, it prevents useless re-renderings when the next props equal to previous ones.

Take precautions when memoizing components that use props as callbacks. Make sure to provide the same callback function instance between renderings.