How to use useCallback and useMemo

1)What is useMemo why do wee need it?

For this article, I assume that you’ve already read my post about React.memo, or at least have some knowledge of it.

Regarding the following code:

1import { memo, useMemo, useState } from 'react';
2
3const ChildComponent = memo( ({ studentInfo }: { studentInfo: { name: string ,age: number}}) =>{
4  
5    useEffect(()=>{
6      console.log('child rerender')
7    })
8
9   return <>{studentInfo.name}, age {studentInfo.age}</> 
10}) 
11
12const ParentComponent = () =>{
13
14  const [age,setAge] = useState(12)
15  const [counter,setCount] = useState(0)
16
17  const studentInfo =  {
18    name: 'John',
19    age
20  }
21
22  return <>
23    <button onClick={()=>{ setAge(prev => prev += 1) }}>increase age</button>
24    <button onClick={()=>{ setAge(prev => prev -= 1) }}>descease age</button>
25    <button onClick={()=>{ setCount(prev => prev += 1) }}>change counter</button>
26
27    <br/>
28    <ChildComponent studentInfo={studentInfo}/>
29    <br/>
30     counter: {counter}
31  </>
32}
33

We have a ChildComponent that’s wrapped with memo, meaning it will re-render only when the props passed to it change. Of course, when you increase or decrease the age in studentInfo, line 6 will log.

So what happens when we change the counter? You can clearly see that studentInfo doesn’t depend on it.

The Child component still re-renders, and line 6 logs — but why??

Every time the parent component re-renders (in this case, when the counter changes), a new studentInfo object is created. React uses shallow comparison to check whether the props have changed. A shallow comparison only checks the top level of objects or arrays — not their nested structures. Therefore, even though the content of studentInfo hasn’t changed, its object reference has, causing the component to re-render.

useMemo is a React Hook that memoizes (caches) the result of a computation between renders — so React only recomputes it when its dependencies change.

Now, let’s use useMemo for our example.

1const studentInfo =  useMemo(()=>{
2    return {
3    name: 'John',
4    age
5  }
6  },[age])
7

After we change the counter, no re-render happens for ChildComponent.

2) useCallback

useCallback is siminlar to useMemo, but is used for functions

Consider this code:

1
2import { memo, useEffect, useState } from 'react';
3
4const ChildComponent = memo( ({ caculateSalary }: { caculateSalary: any}) =>{
5  
6  useEffect(()=>{
7    console.log('child rerender')
8  })
9
10   return <button onClick={()=> console.log(caculateSalary())}>show salary</button> 
11}) 
12
13const ParentComponent = () =>{
14
15  const [salary,setSalary] = useState(1200)
16  const [counter,setCount] = useState(0)
17
18    const caculateSalary =  ()=>{
19    return salary + 100// bonus
20  }
21
22  return <>
23    <button onClick={()=>{ setSalary(prev => prev += 1) }}>increase salary</button>
24    <button onClick={()=>{ setSalary(prev => prev -= 1) }}>descease salary</button>
25    <button onClick={()=>{ setCount(prev => prev += 1) }}>change counter</button>
26
27    <br/>
28    <ChildComponent caculateSalary={caculateSalary}/>
29    <br/>
30     counter: {counter}
31  </>
32}
33
34export { ParentComponent }
35
36

Of course, when we change the counter, a new calculateSalary object will be created. React counts it as a props change, and the Child component re-renders.

We can simply prevent it by using useCallback

1
2  const caculateSalary =  useCallback(()=>{
3    return salary + 100// bonus
4  },[salary])
5
6

Now, calculateSalary is memoized and will only be recreated when its dependency (salary) changes.