This article is part of the React hooks series. If you haven't read the other articles, you can find them at the links below.
- What is useEffect hook and how do you use it?
- What is useReducer hook and how do you use it?
- What is useState hook and how do you use it?
Content outline
useCallback hook
A hook is a special function which enables one use state and other react features without writing ES6 class components, according to React hooks documentation.
The useCallback
hook is part of the React Hooks API. It takes a callback function and an array of dependencies as arguments. It returns a memoized version of the function passed as argument. If the above definition doesn't make sense, the sections below explain what is useCallback
hook, why it is important and how to use it.
In the code below, i have defined two event handlers; eventHandler
which we shall later pass as a prop to Child
component and clickHandler
which is attached as a click event handler on the button element. In React, a component is re-rendered if its state or props change leading to a re-render of its descendants too. For this particular case if we change state of App
, its child component Child
will also re-render. To prevent Child
component from re-rendering if its parent's state changes, i used React.memo
. If you are not familiar with React.memo
, you can read about it Here. If you now click the button, it will fire the event handler clickHandler
which will change state of App
. You will see Parent is rendered
on the console but not Child is rendered
. If we hadn't wrapped Child
component in React.memo
, changing state of App
would make Child
component to be re-rendered but this has been prevented by React.memo
. This way we can be sure that Child
component is only re-rendered due to change in props. You can play with the code Here on Codepen.
import React from "react";
import ReactDOM from "react-dom";
const App = (props) => {
const [state, setState] = React.useState(0);
const eventHandler = (e) => {
console.log(1);
};
const clickHandler = (e) => {
setState((prevState) => prevState + 1);
};
console.log("Parent is rendered");
return (
<div>
<button onClick={clickHandler}> click </button>
<Child />
</div>
);
};
const Child = React.memo((props) => {
console.log("Child is rendered");
return null;
});
const root = document.getElementById("root");
ReactDOM.render(<App />, root);
You can play with the above code Here on Codepen.
Let us now pass eventHandler
as a prop to Child
component. Change only one line: <Child />
. Pass eventHandler
as prop so that we have <Child eventHandler={eventHandler} />
.
The above code becomes:
import React from "react";
import ReactDOM from "react-dom";
const App = (props) => {
const [state, setState] = React.useState(0);
const eventHandler = (e) => {
console.log(1);
};
const clickHandler = (e) => {
setState((prevState) => prevState + 1);
};
console.log("Parent is rendered");
return (
<div>
<button onClick={clickHandler}> click </button>
<Child eventHandler={eventHandler} />
</div>
);
};
const Child = React.memo((props) => {
console.log("Child is rendered");
return null;
});
const root = document.getElementById("root");
ReactDOM.render(<App />, root);
You can play with the above code Here on Codepen.
If you change state of App
by clicking the button, Child
component will also be re-rendered. Both Parent is rendered
and Child is rendered
will be console logged. Before passing eventHandler
to Child
as a prop, a re-render of App
doesn't cause a re-render of Child
but after passing eventHandler
as a prop, Child
is re-renderd. This happens because a re-render of App
creates a new instance of eventHandler
, which has been passed as prop to Child
. React will compare instance of eventHandler
created for the previous render and for current render. The two won't be equal because a function is an object and two objects tested for equality (strict or non-strict) will only evaluate to true if they have the same reference. For example {} === {}
, [] === []
and function(){} === function(){}
will all evaluate to false. Strictly speaking, React uses Object.is
algorithm to compare eventHandler
. This is interpreted by React as change in props resulting in a re-render of Child
. This, most likely, is not what you need.
The above behaviour is, in most cases, undesirable. As a result useCallback
was developed to fix such problems. It takes a function and array of dependencies as argument. If you pass an empty array, React will always return the same memoized instance of function passed as argument. If an event handler is passed as an argument to useCallback
, the same version of the callback is returned in subsequent renders as long as array of dependencies haven't changed. Change eventHandler
to:
const eventHandler = React.useCallback((e) => {
console.log(1)
}, [])
If you now change state of App
, Child
component will not be re-rendered. I hope you notice the empty array passed as second argument. It will ensure the same memoized version of the callback is returned between renders. If you pass an array of dependencies, different memoized version is returned whenever the dependencies change between renders. It should be noted that any value referenced within the callback function (first argument to useCallback
) should be passed as a dependency.
useMemo hook
In the above section, we looked at what useCallback
hook is and how to use it. It is now time to look at another hook closely related to useCallback
, useMemo
. The function signature of useMemo
is similar to that of useCallback
. The difference is that useMemo
returns a memoized value while useCallback
returns a memoized
callback function. Both take a function as first argument and an array of dependencies as second argument.
useMemo
hook is used for performing operations which are computationally expensive (Takes a long time to perform or a lot of computer memory). If the dependencies do not change, memoized
value will always be returned. If you want the computation to be performed once then pass an empty array.
The code below illustrates how useMemo
hook is used.
const App = (props) => {
const [count, setCount] = React.useState(0);
const memoizedValue = React.useMemo(() => {
return Math.random(); //This should be an expensive computation not just generating a random number
}, []);
console.log(memoizedValue);
const clickHandler = (e) => {
setCount(count + 1);
};
return (
<p>
<button onClick={clickHandler}> Click </button>
</p>
);
};
const root = document.getElementById("root");
ReactDOM.render(<App />, root);
You can play with the above code Here on Codepen.
In the code above, i passed a function which generates and returns a random number(ONLY for illustration). Since i passed an empty array as a dependency, the first random number is memoized and returned on every re-render of the component. If i had not passed a dependency, the random value is computed on every render of the component. The value returned is computed again if the dependencies change
Note
You are strongly advised against using
useMemo
hook willy-nilly.useMemo
hook is used if you are peforming an expensive computation. Do not use it for generating random number like i did in the example. It is meant to illustrate how to useuseMemo
hook.
Thanks for reading up to the end. If you find anything technically inaccurate, you can comment below and if you find the article enlightening, share it on Twitter or any other social media platform/community. Others might find it useful too.
Top comments (0)