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:
- To access the DOM:
<div ref={myRef} />
- Store mutable value like instance variable (in class)
- A render counter
- A value to be used in
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:
- Optimize expensive calculations (e.g. operations on data like sort, filter, changing format etc.) while rendering
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:
- Passing memoized functions to child components (that are optimized with
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}/>
}
- Passing memoized functions to as dependencies in other hooks.
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.
useRef
can be used to store local mutable value in a component. It doesn't participate in rerendering (unline state data).useMemo
is used to memoize (like we do in Dynamic Programming, concept wise) and skip recalculation. It is useful when you don't want to recalculate heavy calculations each time a component renders.useCallback
is used to avoid recreating / redefining methods at every render. – Tar