Saturday, 7 December, 2019 UTC


Summary

You may have seen, released along with React Hooks, a strange hook called useMemo. What could this strange, đź‘˝alienđź‘˝ hook mean and what is it used for? Most importantly, how can it help you and why do you need to know about it?
First, a little brush up on JavaScript equality.
Referential Equality
You may remember how Javascript compares objects 🥴. There are some tricky results when we run equality comparisons:
{} === {} // false const z = {} z === z // true 
React uses Object.is to compare components, but that gives very similar results to using ===. So, when React checks for any changes in a component, it may find a “change” that we wouldn’t really consider a change.
() => {} === () => {} // false [] === [] // false 
This comparison check will cause some React re-rendering we didn’t intend or expect. If that re-rendering is some expensive operation, that can hurt performance. If one part re-renders, it re-render the entire component tree. Thus, React released the memo idea to fix this.

✨ Black Friday - 50% off all of Wes Bos' courses ⤵

This affiliate banner helps support the site 🙏
Memoization
You may have heard this fancy word, memoization. Memoization is basically an optimization technique which passes a complex function to be memoized or remembered. In memoization, the result is remembered, when the same exact parameters are passed-in subsequently. Kind of like memorization. If we have a function compute 1 + 1, it will return 2. But if it uses memoization, the next time we run 1’s through the function it won’t add them up, it will just remember the answer is 2 without executing the adding function.
In React, memoization optimizes our components, avoiding complex re-rendering when it isn’t intended. Here is a great article on using React.memo to optimize your application. React.memo acts like a pure component, wrapping your component and the linked article does a great job explaining it. However, useMemo is a bit different in usage.
From the official React documentation, useMemo’s signature looks like this:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); 
As you can see, useMemo takes in a function and a list of dependencies (the array [a, b]). If you are familiar with useEffect, then you have probably seen the dependencies idea. They act similar to arguments in a function. The dependencies list are the elements useMemo watches: if there are no changes the function result will stay the same, otherwise it will re-run the function. If they don’t change, it doesn’t matter if our entire component re-renders, the function won’t re-run but instead return the stored result. This can be optimal if the wrapped function is large and expensive. That is the primary use for useMemo.
useMemo Example
const List = useMemo( () => listOfItems.map(item => ({ ...item, itemProp1: expensiveFunction(props.first), itemProp2: anotherPriceyFunction(props.second) })), [listOfItems] ) 
In the above example, the useMemo function would run on the first render. It would block the thread until the expensive functions complete, as useMemo runs in the render. Initially, this won’t look as clean as useEffect, since useEffect can render a loading spinner until the expensive functions finish and the effects fire off. However, the expensive functions would never fire off again if listOfItems never changed and we would still get the return value from them. It would make these expensive functions seem instantaneous. This is ideal of you have an expensive, synchronous function or two.
Prevent Re-Rendering
If you’re familiar with React’s class-component lifecycle hook, shouldComponentUpdate, useMemo has a similar usage in preventing unnecessary re-renders. Let’s say we have this instance:
function BabyGator({fish, insects}) { // because what else do baby gators eat? const dinner = {fish, insects}; useEffect(() => { eatFunction(dinner); }, [fish, insects]) } function MamaGator() { return <BabyGator fish='small edible fish' insects={15}> } 
This works perfectly well. Our useEffect hook watches the fish and insects props being passed-in (he is hungry). However, this only works with primitive values. That’s the key.
Remember back to talking about Referential Equality? [] === [] // false. That’s exactly what memoizing hooks like useMemo and useCallback are made for. If our insects prop is an array, we can put it in the useMemo hook and it will reference it, after a render, as equal. If a function or another non-primitive value is in the useEffect dependency, it will recreate a new array, because of closure, and find it unequal.
Obviously here, we don’t need useMemo, if all we are doing is memoizing an array. But if there was an expensive function to calculate that array, useMemo would be useful!
When Not to useMemo
The useCallback hook is similar to useMemo, but it returns a memoized function, while useMemo has a function that returns a value. If our dependencies array is empty, there is no possibility of memoization and it will compute a new value on every render. You might as well implement the useRef hook at that point. The advantage useMemo offers over useRef is a re-memoizing if the dependencies change.
When looking to implement useMemo always ask, “is this really an expensive function?” Expensive means it is sucking up a lot of resources (like memory). If you are defining a ton of variables in a function at render, it might make sense to memoize with useMemo.
You won’t want to have useMemo fire off any side effects or any asynchronous calls. Both of those would make more sense to be contained within useEffect.
When looking to implement useMemo, write the code first then revisit it to see if you can optimize it. Don’t start with useMemo. Too many people implement it quickly and it can make performance worse in a small application.
Reference
As always, it’s ideal to visit the official docs. From the docs, you’ll see that the following is also recommended:
We recommend using the exhaustive-deps rule as part of our eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix.