React is a library for creating front end views. It has a big ecosystem of libraries that work with it. Also, we can use it to enhance existing apps.
In this article, we look at the useCallback
, useMemo
, useRef
, and useImperativeHandle
hooks.
useCallback
We can use the useCallback
hook to return a memoized callback.
It takes a callback function as the first argument, and an array of value that changes for the callback in the first argument to be called.
This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.
For example, we can use it as follows:
function App() {
const [count, setCount] = React.useState(0);
const memoizedIncrement = React.useCallback(() => {
setCount(count => count + 1);
}, [setCount]);
const memoizedDecrement = React.useCallback(() => {
setCount(count => count - 1);
}, [setCount]);
return (
<>
Count: {count}
<button onClick={memoizedDecrement}>Decrement</button>
<button onClick={memoizedIncrement}>Increment</button>
</>
);
}
In the code above, we used the useCallback
hook to create 2 new functions to let us update the count
. We have setCount(count => count + 1)
and setCount(count => count — 1)
in the callback of useCallback
.
And we cache the setCount
function so that React won’t always return a new one in every render unless setCount
changes.
Then in the onClick
handlers, we use them as usual.
Every value referenced inside the callback should also appear in the dependencies array.
useMemo
useMemo
caches values that are computed from a function. The first argument is a function that computes the value, and the second argument is an array with the dependencies that are used to compute the returned value.
useMemo
runs during rendering. It’s used as a performance optimization. React may forget previously memoized value and recalculate them on the next render in cases it needs to free memory for offscreen components for example.
Therefore, we should write code that works with useMemo
and then add it to optimize performance.
For example, we can use it as follows:
function App() {
const [count, setCount] = React.useState(0);
const doubleCount = React.useMemo(() => 2 * count, [count]);
return (
<>
Double Count: {doubleCount}
<button onClick={() => setCount(oldCount => oldCount - 1)}>
Decrement
</button>
<button onClick={() => setCount(oldCount => oldCount + 1)}>
Increment
</button>
</>
);
}
In the code above, we use the useMemo
hook by passing in a function that returns 2 times the count
, and pass in an array with count
inside as the 2nd argument.
useMemo
caches the value of doubleCount
until count
changes.
Then we pass in functions to call setCount
in the onClick
props of the buttons.
Finally, we display the doubleCount
value on the screen, which updates when we click on the buttons.
useRef
The useRef
hook returns a mutable ref whose current
property is initialized to the passed argument. The returned object will persist for the full lifetime of the component.
For example, we can use it as follows:
function App() {
const inputEl = React.useRef(null);
React.useEffect(() => {
inputEl.current.focus();
}, []);
return (
<>
<input ref={inputEl} type="text" />
</>
);
}
In the code above, we created the inputEl
ref by calling useRef
with the value null
.
Then once we pass inputEl
as the ref
of the input, we set inputEl.current
to the input element’s DOM object.
Therefore, we can call focus
on it to focus the input in the useEffect
callback, which is only run during initial render, since we passed in an empty array to the second argument.
It can be used to keep any mutable value around. useRef
creates a plain Javascript object.
The only difference between useRef()
and creating a { current: ... }
object ourselves is that useRef
will give us the same ref object on every render.
useRef
doesn’t notify us when the content changes.
useImperativeHandle
useImperativeHandle
customizes the instance value that’s exposed to parent components when using a ref
.
For example, we can use it as follows:
function Button(props, ref) {
const buttonRef = React.useRef();
React.useImperativeHandle(ref, () => ({
focus: () => {
buttonRef.current.focus();
}
}));
return <button ref={buttonRef}>Button</button>;
}
Button = React.forwardRef(Button);
function App() {
const buttonRef = React.useRef(null);
React.useEffect(() => {
buttonRef.current.focus();
}, []);
return (
<>
<Button ref={buttonRef} />
</>
);
}
In the code above, we have:
React.useImperativeHandle(ref, () => ({
focus: () => {
buttonRef.current.focus();
}
}));
to customize the focus
method to call buttonRef.current.focus();
.
Then we pass buttonRef
in Button
to the button
‘s ref
as follows:
<button ref={buttonRef}>Button</button>;
Then to make the ref accessible to App
, we run:
Button = React.forwardRef(Button);
Then in App
, we run:
const buttonRef = React.useRef(null);
to create the ref and:
<Button ref={buttonRef} />
to set buttonRef
to our exposed Button
‘s buttonRef
after calling forwardRef
to expose it to App
.
Then we run:
React.useEffect(() => {
buttonRef.current.focus();
}, \[\]);
to focus the Button
component’s button
when App
first renders.
Conclusion
We can use the useCallback
hook to return a memoized callback, which we can call. It takes a callback as an argument and an array of dependencies that were referenced in the callback as the second argument.
useMemo
caches values that are computed. It takes a function that returns a value as the first argument, and an array of values that the function depends on as the second argument.
useRef
returns a mutable object whose current
property is initialized to the initial value and can be passed into the ref
prop of an element to set current
to the DOM element.
useImperativeHandle
customizes the behavior of the DOM element methods of the element that’s exposed via forwardRef
to the parent component.