Optimize Your React Functional Components with useCallback
and useMemo
Learn how to use useCallback
and useMemo to optimize performance

In our last posts we have looked at hooks:
useState
useReducer
useContext
Continuing with the Hooks series, in this article, we will be looking at the useCallback
and useMemo
hooks and how they help optimize our functional components.
Bundle and share your components in the cloud, reuse them across applications, suggest updates from any app and build faster as a team. Try it.
use them in different apps, suggest updates from any project, and build better as a team.
A quick tip: Use Bit (Github) to share, reuse and update your React components across apps. Bit tracks and bundles reusable components in your projects, and exports them encapsulated with their dependencies, compilers and everything else. Components can then be installed with package managers, and even updated right from any new project. Give it a try.

useMemo
This is used to memoize functions. Woah! memoize? What does it mean? memoize is the action of storing a result of input(s) and returning the result when the input(s) occur again. You have heard of memoization, right? That’s the act of memoizing functions.
To understand memoizing, let’s follow this analogy. Take, for example, your Teacher calls you in front of the class and assigns you to give him the result of any multiplication. Now, he gives you a multiplication table where you can look them up. You might find it tasking to look up any multiplication asked to you. You can memorize the ones asked repeatedly and give back the answer without checking the table.
Let’s say he (your Teacher) asks you the below questions in a span of 1 hour:
4 * 87 = 348
5 * 7 = 35
667 * 66 = 44022
6 * 76 = 456
4 * 87 = 348
5 * 88 = 440
667 * 66 = 44022
7 * 6 = 42
4 * 87 = 348
667 * 66 = 44022
4 * 87 = 348
See 4 * 87
was asked four times and 667 * 66
thrice. You will have to memorize them so when asked again you won't have to look at the multiplication table, you'll just give the answer because you have already memorized it. No costly lookups.
The same happens in memoization. Memoriz
ation Memoiz
ation, they sound the same. I guess riz
was changed to iz
in the programming world so they would have different pronunciation :).
In functions, its inputs/arguments can be memoized, let’s say we have an expensive function that takes 3 mins to execute:
const waitSync = (ms) => {
// simulate running for `ms` time
}function expensiveFunction(a) {
waitSync(180000) // runs for 3 mins
return a * 34;
}
If we call the expensiveFunction with value 4 like this:
const call1 = expensiveFunction(4) // 3 mins
const call2 = expensiveFunction(4) // 3 mins
const call3 = expensiveFunction(4) // 3 mins// Total: 9 mins
expensiveFunction was called with value 4 thrice, it will take 9 mins for our script to execute!!! The expensiveFunction should remember or memorize inputs that occur more than twice so it doesn’t have to re-calculate them each time. We have to make our expensiveFunction function to remember previous inputs and store its result so when the inputs occur again it simply returns from the store bypassing the 3-min wait.
function expensiveFunction(a) {
expensiveFunction.cache = expensiveFunction.cache || {}
if(expensiveFunction.cache.a) {
return expensiveFunction.cache.a
}
waitSync(180000) // runs for 3 mins
return expensiveFunction.cache.a = a * 34;
}
See we have memoized our expensiveFunction function, it now first check for any reference of the input a
in its cache object, if found it returns the value, if not it performs the calculation and stores the result in the cache object with the input as the key.
With this let’s re-run our script:
const call1 = expensiveFunction(4) // 3 mins
const call2 = expensiveFunction(4) // 0.03s
const call3 = expensiveFunction(4) // 0.03s// Total: 3.006 mins
See the first call took 3 mins normally, but the second and last calls took more than one-quarter of the first call!! The thing here is that in the first it cached the input with its result so when we called it the second time it just returned the result from the cache instead of recalculating for 3 mins.
Instead of directly writing our memoization algo. into our functions we can create a function that memoizes function passed to it:
function memoize(fn) {
return function() {
var args =
Array.prototype.slice.call(arguments)
fn.cache = fn.cache || {};
return fn.cache[args] ? fn.cache[args] : (fn.cache[args] = fn.apply(this, args))
}
}
So we can pass our original expensiveFunction
to memoize
like this:
const memoizedExpensiveFunction = memoize(expensiveFunction);const istCall = memoizedExpensiveFunction(90); // 3 mins
const secondCall = memoizedExpensiveFunction(90); // 0.03s
const thirdCall = memoizedExpensiveFunction(90); // 0.03s
The function memoize
memoizes function passed to it and returns a higher-order function that implements the memoization algorithm.
Now, we have seen and know how memoization works. The hook useMemo
works the same way, we supply it a function and input dependencies. It evaluates the function with the inputs first and returns the result. The general form of useMemo
is this:
const memoizedOutput = useMemo(create: ()=> mixed, inputs: Array<mixed> | void | null)
create
is the function to be memoized, inputs
is the array of inputs that the function create
needs to work with. If the input changes the memoizedOutput
will be re-calculated.
Let’s see an example:
function App() {
const [count, setCount] = useState(0)
const expFunc = (count)=> {
waitSync(180000);
return count * 90;
} const resCount = expFunc(count) return (
<>
Count: {resCount}
<input type="text" onChange={(e)=> setCount(e.target.value)} placeholder="Set Count" />
</>
)
}
We have an expensive function expFunc
that takes 3 mins to execute, it takes an input count
waits for 3 mins before returning the multiple of 90
. We have a variable resCount
that calls the expFunc
with the count
variable from the useState
hook. We have an input that sets the count
state whenever we type anything.
Whenever we type anything, our App component is re-rendered causing the expFunc
function to be called. We will see that if we type continuously the function will be called causing a massive performance bottleneck. For each input, it will take 3 mins for it to be rendered. If we type 3
, the expFunc will be run for 3 mins and if we type 3
again, it will take 3 mins again. It shouldn't run again in the second input because its the same as the previous input, it should store the result somewhere and return it without running the function (expFunc
).
Try the above in your browser
We will use the useMemo
hook to memoize the expFunc
function. Remember we said that the useMemo hook accepts an optional array of inputs that it checks when changed it runs the function passed to it. So to memoize expFunc
we will pass it to the useMemo hook and also pass in the count
state from the useState hook because that's the expFunc
function input:
const resCount = useMemo(()=> {
return expFunc(count)
}, [count])
In our App component, it will look like this:
function App() {
const [count, setCount] = useState(0)
const expFunc = (count)=> {
waitSync(180000);
return count * 90;
} const resCount = useMemo(()=> {
return expFunc(count)
}, [count]) return (
<>
Count: {resCount}
<input type="text" onChange={(e)=> setCount(e.target.value)} placeholder="Set Count" />
</>
)
}
Now, if we type 3
, it is the first, so the expFunc
will be called and it will take 3 mins for us to see the value resCount
to be rendered. If we type 3
again, the expFunc
will not be called and there will be no 3
mins delay because useMemo
will return the result from the cache.
With this, we have optimized our App component to be highly efficient.
We can also wrap the return value of our functional component in a useMemo callback to memoize, the component would re-render but the return value will be based on its dependencies, if changed will return a new value, if not will return cached version.
If we call our expFunc in the JSX like this:
function App() {
const [count, setCount] = useState(0)
const expFunc = (count)=> {
waitSync(180000);
return count * 90;
} return (
<>
Count: {expFunc(count)}
<input type="text" onChange={(e)=> setCount(e.target.value)} placeholder="Set Count" />
</>
)
}
The return value is a JSX that React uses to create a virt. DOM tree and append it to the browser DOM. So if App continually re-renders it will take 3 mins before we see an update. If we don’t want to separate the logic, we will pass the return statement to a useMemo callback function.
function App() {
const [count, setCount] = useState(0);
const expFunc = (count)=> {
waitSync(180000);
return count * 90;
} return useMemo(()=> {
return (
<>
Count: {expFunc(count)}
<input type="text" onChange={(e)=> setCount(e.target.value)} placeholder="Set Count" />
</>
)
}, [count])
}
The App component will be re-executed but it should be cheap because it does nothing. A new value of JSX will be returned when the count state changes. You see expFunc won’t be re-executed unnecessarily. If we type 1, useMemo would execute its callback and return the JSX markup, so expFunc would be executed. If we type 1 again useMemo would see the same value of count as of last time and won’t execute its callback, so expFunc won’t be executed.
Note: React.memo
and React.useMemo
uses the same technique for optimization but there use cases differs. React.memo
is used on components only but useMemo
can be used on both components and standalone functions.
In useMemo
there are many mistakes devs new to it usually make. Whenever we want to memoize a function:
function toBeMemoed(input) {
// ...
}
We dont pass the function like this to useMemo:
useMemo(toBeMemoed, [input])
This won’t work because React won’t call toBeMemoed
function with the input so the function won't be memoized. The correct way is to call the function toBeMemoed
inside a function and pass it to the useMemo
hook.
useMemo(()=> {
return toBeMemoed(input)
}, [input])useMemo(()=> toBeMemoed(input), [input])
useMemo
naturally expects a callback function, then expects we call the function we want memoize inside the callback body. In truth, useMemo memoizes the callback function but since our own to-be-memoized function is called inside the callback it's in turn memoized. It will execute when useMemo executes its callback. Also, we have to return the value of our to-be-memoized function we called inside the callback body to get the latest value.
We can use the useMemo hook to optimize useState, useReducer, useContext hooks. That will be a very long post if we delve into that, we will leave it for another article Optimising Hooks: Bail out of Hooks
useCallback
This works as useMemo but the difference is that it’s used to memoize function declarations.
Let’s say we have this:
function TestComp(props) {
l('rendering TestComp')
return (
<>
TestComp
<button onClick={props.func}>Set Count in 'TestComp'</button>
</>
)
}TestComp = React.memo(TestComp)function App() {
const [count, setCount] = useState(0) return (
<>
<button onClick={()=> setCount(count + 1)}>Set Count</button>
<TestComp func={()=> setCount(count + 1)} />
</>
)
}
We have an App component that maintains a count state using useState, whenever we call the setCount function the App component will re-render. It renders a button and the TestComp component, if we click the Set Count button the App component will re-render along with its child tree. Now, TestComp is memoized using memo to avoid unnecessary re-renders. React.memo memoizes a component by comparing its current/next props with its prev props if they are the same it doesn’t re-render the component. TestComp receives a prop actually a function in a func props attribute, whenever App is re-rendering, the props func of TestComp will be checked for sameness, if found being the same it will not be re-rendered.
The problem here is that TestComp receives a new instance of the function prop. How? Look at the JSX:
...
return (
<>
...
<TestComp func={()=> setCount(count + 1)} />
</>
)
...
An arrow function declaration is passed, so whenever App is rendered a new function declaration is always created with a new reference(memory address pointer). So the shallow comparison of React.memo will record a difference and will give a go-ahead for re-rendering.
Now, how do we solve this problem? Should we move the function outside of the function scope, it will be good but it won’t have reference to the setCount function. This is where useCallback comes in, we will pass the function-props to the useCallback and specify the dependency, the useCallback hook returns a memoized version of the function-prop that’s what we will pass to TestComp.
function App() {
const check = 90
const [count, setCount] = useState(0)
const clickHndlr = useCallback(()=> { setCount(check) }, [check]); return (
<>
<button onClick={()=> setCount(count + 1)}>Set Count</button>
<TestComp func={clickHndlr} />
</>
)
}
Here, clickHndlr
will not be re-created in every re-render of App
component unless it dependency check
changes, so when we repeatedly click on Set Count
button TestComp
will not re-rendered. useCallback
will check the check
variable if not same as its prev value it will return the function passed so TestComp
and React.memo
would see a new reference and re-render TestComp
, if not same useCallback
would return nothing so React.memo
would see a function reference same as its prev value and cancel re-render of the TestComp
.
Conclusion
We have seen how useMemo and useCallback help us write a highly optimized React app. They both use memoization to make sure we don’t have wasted renders in our apps.
The knowledge of useMemo
and useCallback
isn't enough, you have to know how to create and organize your components tree, know when to use presentational components and smart components because we need when to split components into two or more to add memoization. With all this, we can make a near perfect app with 100% performance rate.
If you have any question regarding this or anything I should add, correct or remove, feel free to comment, email or DM me.
Thanks !!!