We all know how to use React.useCallback
and React.useMemo
, but sometimes we will be confused about which one we should choose.
Here I will show you guys some cases to help you understand what hook function we should use in some specific situations.
import React, { useState } from 'react'
export default function App() {
const [num1, setNum1] = useState(10)
const [num2, setNum2] = useState(12)
return <div>{num1 + num2}</div>
}
In the above simple example case, we can just get the sum by num1 + num2
in the HTML element part, but if we have some more complex calculation logic, we shouldn't do that anymore.
import React, { useState, useMemo } from 'react'
export default function App() {
const [num1, setNum1] = useState(10)
const [num2, setNum2] = useState(12)
const sum = useMemo(() => {
const _num1 = Number(num1),
_num2 = Number(num2)
if (Number.isNaN(_num1) || Number.isNaN(_num2)) {
return "check your num's type"
}
return _num1 + _num2
}, [num1, num2])
return <div>{sum}</div>
}
In this example code block, we chose useMemo
to keep the result value of num1 + num2
, but we add some type check logic, because we can't totally trust the num1 or num2's type would be number, so when the type goes wrong, we will show the placeholder text.
In this situation, you can't realize this effect in the HTML part with simple code in one line. Of course, you can write some logic code in the HTML part with conditional operators (JSX allow you to do that), but the cost is sacrificing the readability of the code.
So, useMemo
is a good choice to handle this case.
Ok, let's go on one more complex case.
import React, { useState, useMemo } from 'react'
export default function App() {
const [num1, setNum1] = useState(10)
const [num2, setNum2] = useState(12)
const [num3, setNum3] = useState(100)
const [num4, setNum4] = useState(120)
const sum1 = useMemo(() => {
const _num1 = Number(num1),
_num2 = Number(num2)
if (Number.isNaN(_num1) || Number.isNaN(_num2)) {
return "check your num's type"
}
return _num1 + _num2
}, [num1, num2])
return (
<>
<div>{sum1}</div>
<div>{num3 + num4}</div>
</>
)
}
As you can see, we have num3 and num4 here to render the sum of them. If we also want to reuse the logic of num1 + num2
, what should we do? Of course we can still use useMemo
to define one sum2, but it's not a good idea to reuse the logic to validate the num's type. So we need split the validate logic out from the useMemo
. Then useCallback
shows up!
import React, { useState, useCallback } from 'react'
const validteNumAndSum = (number1, number2) => {
const _num1 = Number(number1),
_num2 = Number(number2)
if (Number.isNaN(_num1) || Number.isNaN(_num2)) {
return "check your num's type"
}
return _num1 + _num2
}
export default function App() {
const [num1, setNum1] = useState(10)
const [num2, setNum2] = useState(12)
const [num3, setNum3] = useState(100)
const [num4, setNum4] = useState(120)
const sumFunc = useCallback(validteNumAndSum, [])
return (
<>
<div>{sumFunc(num1, num2)}</div>
<div>{sumFunc(num3, num4)}</div>
</>
)
}
As you can see, we use useCallback
to return a new function with the logic of validating num's type and calculating the sum, and we reuse this validteNumAndSum
function in the HTML part easily.
Ok, let's go to the conclusion part:
If you can easily make a calculation (math or more complex situation), you don't need both of them useMemo
and useCallback
.
If your calculation logic is complex and just needs to be calculated once only, you can pick the useMemo
hook to package your calculation process and return one simple result.
But if you would use the same logic more than one time, you can choose useCallback
to return a memorized function to reuse it in your component.
I am so sorry about useCallback
case. I made a mistake that is to explain when to choose useCallback
and created a bad example case.
Here I will try again.
import React, { useState, useCallback } from 'react'
export default function App() {
const [base, setBase] = useState(0)
const [num1, setNum1] = useState(10)
const [num2, setNum2] = useState(12)
const [num3, setNum3] = useState(100)
const [num4, setNum4] = useState(120)
const sumFunc = useCallback(
(number1, number2) => {
const _num1 = Number(number1),
_num2 = Number(number2)
if (Number.isNaN(_num1) || Number.isNaN(_num2)) {
return "check your num's type"
}
return _num1 + _num2 + base
},
[base],
)
return (
<>
<input
type="number"
value={base}
onChange={e => {
setBase(Number(e.target.value))
}}
/>
<div>{sumFunc(num1, num2)}</div>
<div>{sumFunc(num3, num4)}</div>
</>
)
}
In the code above, we add one base
number as another variable data, and every sum should be added with the base
value, and we have an input
to change the value of base
, so every time base
is changed and sumFunc
would be modified too, and we can use it to calculate the right result.
So I will update my conclusion about useCallback
: if we just need to reuse some logic, we can package this logic into a function and even split it out from the component as a util function to reuse it anywhere. And if our logic still relies on some variable of the component, we can use useCallback
to package the logic and return a memorized new function.