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. Memo riz ation Memo iz 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 !!!