Built-in React hooks — useCallback, useMemo, and Refs

Benjamin
4 min readMar 24, 2023

React is a popular front-end library that has a vast ecosystem of libraries working with it. As a result, it has become an essential tool for web developers looking to create efficient and dynamic user interfaces. One of the most significant advantages of React is its hooks system, which allows developers to manage state and lifecycle methods more intuitively and flexibly. This article will explore four hooks that can improve React performance: useCallback, useMemo, useRef, and useImperativeHandle.

useCallback

The useCallback hook is used to return a cached callback function. The first argument is the cached callback function, and the second is an array of dependencies that will trigger the cached process to be called when they change. This hook is useful when passing callbacks to optimized child components that rely on reference equality to avoid unnecessary rendering.

For instance, suppose we have a React component with a count state that we want to increment and decrement. To optimize performance, we can store the setCount function in the cache using the useCallback hook. The cached role will only be re-evaluated if the count state changes.

import { useCallback, useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

const increment = useCallback(() => {
setCount(c => c + 1);
}, []);

const decrement = useCallback(() => {
setCount(c => c - 1);
}, []);

return (
<>
<h1>{count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</>
);
}

In the above example increment and decrement functions are wrapped with the useCallback hook, which memoizes the function instances between renders. Since the callback functions don't depend on any changing values, the dependency array is empty. This way, the child components that receive these callbacks can optimize their rendering based on reference equality.

useMemo

The useMemo hook caches values computed from a function. The first argument is a function that calculates the weight, and the second argument is an array of dependencies used to add the returned value. This hook is used for performance optimization, as React may forget previously cached values and compute them again on the next render when freeing up memory for off-screen components. Therefore, we should write code that works with useMemo and then add it to optimize performance.

For instance, suppose we have a React component with a count state that we want to double. The useMemo hook can cache the double-count value until the count state changes. This way, we avoid re-calculating the weight on every render.

import { useMemo, useState } from 'react';

function DoubleCounter() {
const [count, setCount] = useState(0);

const doubleCount = useMemo(() => {
console.log('calculating doubleCount');
return count * 2;
}, [count]);

return (
<>
<h1>Count: {count}</h1>
<h1>Double Count: {doubleCount}</h1>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</>
);
}

In the above example, the doubleCount value is calculated using the useMemo hook. This way, the value is cached until the count state changes. This can be useful when computing expensive values that stay the same.

useRef

The useRef hook returns a mutable reference whose current property is initialized with the passed argument. The returned object remains for the entire lifetime of the component. This hook is useful when accessing DOM elements or managing instance variables in a component.

For example, suppose we have a React component with an input element that we want to focus on when the component mounts. In that case, we can use the useRef hook to create a ref to pass to the input element. We can then call the focus method on the input element inside the useEffect callback, which only runs on the initial render since we pass an empty array as the second argument.

import { useEffect, useRef } from 'react';

function FocusInput() {
const inputRef = useRef(null);

useEffect(() => {
inputRef.current.focus();
}, []);

return (
<>
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
</>
);
}

In the above example, the inputRef is created using the useRef hook. This way, we can pass the reference to the input element and focus it programmatically inside the useEffect hook.

useImperativeHandle

Using the ref prop, the useImperativeHandle hook adjusts the instance value exposed to parent components. It is useful when a child component needs to disclose imperative methods to its parent component. This hook should only be used when other themes cannot provide functionality.

For instance, suppose we have a React component with a button element that we want to focus on when the component mounts. In that case, we can use the useRef hook to create a ref to pass to the button element. We then pass the ref to the Button component using the forwardRef function. Next, inside the useImperativeHandle hook inside the Button component to expose the focus method to the parent component. Finally, we call the focus method inside the useEffect callback to focus on the button element.

import { forwardRef, useImperativeHandle, useRef } from 'react';

const Button = forwardRef((props, ref) => {
const buttonRef = useRef(null);

useImperativeHandle(ref, () => ({
focus: () => {
buttonRef.current.focus();
},
}));

return (
<button ref={buttonRef} {...props}>
{props.children}
</button>
);
});

function FocusButton() {
const buttonRef = useRef(null);

useEffect(() => {
buttonRef.current.focus();
}, []);

return <Button ref={buttonRef}>Click me</Button>;
}

In the above example, we create a Button component that receives a ref prop using the forwardRef function. We use the useImperativeHandle hook to expose the focus method to the parent component inside the component. Finally, we call the focus method inside the useEffect hook to focus on the button element.

Conclusion

In conclusion, React hooks are an essential tool for optimizing React performance. The useCallback, useMemo, useRef, and useImperativeHandle hooks allow developers to manage state and lifecycle methods more intuitively and flexibly. As a result, developers can optimize their React applications and create efficient and dynamic user interfaces by using these hooks.

--

--