Outside of the counter example seen in many YouTube tutorial videos, what are practical/real-world use cases for useMemo
and useCallback
?
Also, I've only seen an input focus example for the useRef
hook.
Please share other use cases you've found for these hooks.
useRef
:Syntax: const refObject = useRef(initialValue);
It simply returns a plain JavaScript object. Its value can be accessed and modified (mutability) as many times as you need without worrying about "rerender".
Its value will persist (won't be reset to the initialValue
unlike an ordinary* object defined in your function component; it persists because useRef
gives you the same object instead of creating a new one on subsequent renders) for the component lifetime.
If you write const refObject = useRef(0)
and print refObject
on console, you would see the log an object - { current: 0 }
.
*ordinary object vs refObject, example:
function App() {
const ordinaryObject = { current: 0 } // It will reset to {current:0} at each render
const refObject = useRef(0) // It will persist (won't reset to the initial value) for the component lifetime
return <>...</>
}
Few common uses, examples:
<div ref={myRef} />
setTimeout
/ setInterval
without a stale closure issue.useMemo
:Syntax: const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
It returns a memoized value. The primary purpose of this hook is "performance optimization". Use it sparingly to optimize the performance when needed.
It accepts two arguments - "create" function (which should return a value to be memoized) and "dependency" array. It will recompute the memoized value only when one of the dependencies has changed.
Few common uses, examples:
Unmemoized example:
function App() {
const [data, setData] = useState([.....])
function format() {
console.log('formatting ...') // this will print at every render
const formattedData = []
data.forEach(item => {
const newItem = // ... do somthing here, formatting, sorting, filtering (by date, by text,..) etc
if (newItem) {
formattedData.push(newItem)
}
})
return formattedData
}
const formattedData = format()
return <>
{formattedData.map(item => <div key={item.id}>
{item.title}
</div>)}
</>
}
Memoized example:
function App() {
const [data, setData] = useState([.....])
function format() {
console.log('formatting ...') // this will print only when data has changed
const formattedData = []
data.forEach(item => {
const newItem = // ... do somthing here, formatting, sorting, filtering (by date, by text,..) etc
if (newItem) {
formattedData.push(newItem)
}
})
return formattedData
}
const formattedData = useMemo(format, [data])
return <>
{formattedData.map(item => <div key={item.id}>
{item.title}
</div>)}
<>
}
useCallback
:Syntax: const memoizedCallback = useCallback(() => { //.. do something with a & b }, [a, b])
It returns a memoized function (or callback).
It accepts two arguments - "function" and "dependency" array. It will return new i.e. re-created function only when one of the dependencies has changed, or else it will return the old i.e. memoized one.
Few common uses, examples:
React.memo
or shouldComponentUpdate
using shallow equal - Object.is
) to avoid unnecessary rerender of child component due to functions passed as props.Example 1, without useCallback
:
const Child = React.memo(function Child({foo}) {
console.log('child rendering ...') // Child will rerender (because foo will be new) whenever MyApp rerenders
return <>Child<>
})
function MyApp() {
function foo() {
// do something
}
return <Child foo={foo}/>
}
Example 1, with useCallback
:
const Child = React.memo(function Child({foo}) {
console.log('child rendering ...') // Child will NOT rerender whenever MyApp rerenders
// But will rerender only when memoizedFoo is new (and that will happen only when useCallback's dependency would change)
return <>Child<>
})
function MyApp() {
function foo() {
// do something
}
const memoizedFoo = useCallback(foo, [])
return <Child foo={memoizedFoo}/>
}
Example 2, without useCallback
, Bad (But eslint-plugin-react-hook
would give you warning to correct it):
function MyApp() {
function foo() {
// do something with state or props data
}
useEffect(() => {
// do something with foo
// maybe fetch from API and then pass data to foo
foo()
}, [foo])
return <>...<>
}
Example 2, with useCallback
, Good:
function MyApp() {
const memoizedFoo = useCallback(function foo() {
// do something with state or props data
}, [ /* related state / props */])
useEffect(() => {
// do something with memoizedFoo
// maybe fetch from API and then pass data to memoizedFoo
memoizedFoo()
}, [memoizedFoo])
return <>...<>
}
These hooks rules or implementations may change in the future. So, please make sure to check hooks reference in docs. Also, it is important to pay attention to eslint-plugin-react-hook warnings about dependencies. It will guide you if omit any dependency of these hooks.