2.2.6: React useMemo - useCallback
Introduction
We’ve talked about useState
and useEffect
which is the building block of all React application. You can arguably create any React project with just these two hooks.
As your app scales, you might find that these hooks alone might not be enough to create an optimised application because there’ll be lots of state changes and side-effect that will be happening and it might slow your app down.
This is why React comes with some built-in hooks to help alleviate this
Here’s a list of additional React hooks from their documentation.
Before we dive in, let’s take a look at one of the technique React uses called memoization
Memoization
So what is memoization?
From wikipedia -
… memoization is an optimisation technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again
The key takeaways are:
Optimisation technique
Speed things up
Returns cache result if input is the same
Let’s take a look at an example of an expensive function call by implementing the Fibonacci number of N sequence
As you can see, if you were to find the “50th” sequence in Fibonacci’s number using the code above, things start to break*.* The reason it broke is because our recursive call still calculates the value of the previous functions even when it has calculated it before
Example of how it looks like
This is where memoization comes in, so let’s optimise it by returning the cache result of our function instead!
We’ve introduced an additional param to our function called cache
and set the value of it to be an empty array if it doesn’t exists.
This stores the previous value of our result and we send the value to our function so that the program doesn’t need to recompute the value again and again and again and again and again.
This will lay the foundation of how memoization works
React.memo
One of the API that React provides us that helps with performance is React.memo
React.memo
is a higher order component and is used as a performance optimisation by memoizing the result
Take for example a button component below
When my App
loads, it will render the Button
component
Inside my App
, if a user types in the input, it will call the function changeText
which will call setText
and re-render the components
This might seem minor but on a page with many components, we don’t really want a simple Button
component to re-render every time a user types right?
This is where React.memo
comes in and is a handy way to optimise unnecessary re-renders
By simply wrapping it in React.memo
whenever state changes in my App
it will not cause a re-render of the Button
component! React will skip rendering the component, and reuse the last rendered result.
💡 A good way to know when to use it for a component is if the component renders the same result given the same props.
useMemo
Returns a memoized value.
This is the equivalent of React.memo
that we encountered except that it’s for values.
Below, we changed the Button
component to track a count
state. Every-time we click the button, we will add 1
to count
. We also implemented the fibonacci sequence from above and plugged it into our App
Notice how whenever we clicked on the Button
component to add to the counter, our whole App
re-renders again and we will have to recalculate our fibonacci number even though the num
state didn’t change.
This is an issue especially if the number is big and our function will be really expensive to compute.
Luckily, this is where useMemo
comes in and optimises the performance by skipping the calculation part if the input hasn’t changed! In essence, we memoized the result and from useMemo
and we keep track of the input of num
in the dependency array.
useCallback
If you ran the code above, you’ll actually realise that our previous Button
component that we wrapped in React.memo
actually is re-rendering again.
Hmm, but nothing changed right? If you look a little closer, this time we are passing in a prop called handleOnClick
which takes in a function.
Well if you passed in a primitive value such as a string
or an integer
, it won’t cause a re-render if the value remained the same, but why did passing in a function
caused an issue?
The real reason is because functions are compared by reference and not by value. The code below illustrates this example:
Whenever React re-renders, the function will be re-generated on every single render, producing a unique function each time. That is to say that the function we passed on to our Button
component has “changed” on each render, causing it to re-render again!
Fortunately, React has provided us with the useCallback
hook that will allow us to keep that function that we created to be the same handleCounterClick
every time.
The Button
component now will not re-render unnecessarily and we have optimised our App
to be more performant.
Conclusion
Despite learning how to make our app more performant, it’s not always necessary to use these hooks. Remember that they are there as tools to help when things feel sluggish or slow. React is a very powerful library that knows how to optimise itself even without using the hooks that we have learned.
Here’s a great article on when you should use the hooks that we have learned by Kent C. Dodds
When to useMemo and useCallback
Resources
If Here’s a really great resource by Josh W Comeau that takes deep dive into what we just went through.