Optimizing a React application with React memo.

Optimizing a React application with React memo.

Application of memo, useMemo and useCallBack in a React application

Introduction

Memoization in programming is an optimization technique used to make applications more efficient and hence faster. Memoization eliminates repeated computation of results gotten from the same input, this is done by storing computation results in a cache and retrieving that same information from the cache the next time it's needed thereby preventing it from being computed again. This same concept is applied in React to optimize our applications. Let's explore various forms in which we can apply memoization in react.

Using the memo Higher Order Component

Take a look at the code sample below

Each time the "count" state is incremented, the App component is re-rendered along with the SubComponent.

But by using the "memo" higher-order component(HOC) given to use by React we can memoize the SubComponent so React doesn't have to re-render the component.

Now we observe that after we've used the memo HOC the SubComponent no longer re-renders unnecessarily because the React now caches the rendering of the SubComponent. Even when we give props to our SubComponent we still get the same result:

So, how does this work? When we increment the count, causing a re-render of the "App" component, any values or state initialized in the App component such as the "name" state will be recreated. Now when we wrap a component with the memo HOC as we've done to the SubComponent, React also checks if the props of the component have changed by comparing the previous and current values of the props.

If the props have not changed it optimizes our flow and uses the previously cached rendering of the component and it prevents an unnecessary re-render. This is what happens when we increment the count state with the "Increment" button.
However, if the props have changed, the SubComponent proceeds with React's diffing process and is re-rendered with the updated props. This is what happens when we click the "Toggle name" button, the "name" state being passed as a prop changes thereby re-rendering the SubComponent as we would want.

The UseMemo Hook

Our last example works fine but there is a certain problem we may encounter in which the memo HOC alone will not be enough to solve. We could pass props made from primitive types (number, string, boolean, null, undefined ) such as the "name" prop we used early without any issue but when we pass nonprimitive types (objects, arrays, functions) as props our memoization essentially breaks and the SubComponent will re-render when the state of the "count" changes.

Below, we pass an object as a prop to the SubComponent and as expected the memo breaks and the SubComponent is re-rendered with each increment of the count state.

This is because React is only doing a shallow comparison between the previous and new values of the props, and non-primitive types need a much deeper kind of comparison to be able to determine if two values are truly the same.

React does this for performance reasons because a much deeper comparison will take longer and use up more resources. How do we fix this, this is where the useMemo Hook comes in.

Now with the useMemo Hook, we cache the object so it isn't re-created on every re-render of the App component. So even after the App component re-renders, the object passed as a prop to our SubComponent remains the same. So our memoization is restored and the SubComponent doesn't re-render when we increment our count.

This state where we've made the prop's previous and updated value point to the reference in memory is known Referential equality. Referential equality problems occur when we are dealing with non-primitive types and in React they are solved using the useMemo and useCallBack hooks.

The useMemo Hook also comes with a dependency array, the object we passed as a prop will only change if the values of those dependencies are updated, similar to a useEffect hook.

Other Use Cases for useMemo

In the above code sample, we have a useEffect hook and a dependency. We would expect that the code in the useEffect hook runs only when the component is mounted or when the value of the dependency is changed.

However, this is not the case, whenever the "count" state is incremented, the code in useEffect will also run.

This is similar to the problem we encountered earlier, this is because we are passing a non-primitive type value in the dependency array. In this case, when the component is re-rendered, the "bio" object is re-created, and though the key and value pairs will have the same value as they did before the re-render, the new "bio" object is not the same. And so in reality, the "bio" object is changed on every re-render, therefore the code in the useEffect will run with each re-render.
To solve this we can use the useMemo hook to cache the object and use the cached object as the useEffect dependency instead.

Using useMemo, the "bio" object in the useEffect dependency remains the same with each re-render and only changes depending on what dependencies we decide to put in the useMemo dependency array. Therefore, the code in the useEffect doesn't run unless the bio is changed.

Another use case for useMemo hook is to prevent expensive calculations on every re-render. We can cache values resulting from expensive or time-consuming computations by using the useMemo hook, thereby preventing them from being re-calculated until any value we place in the dependency array of the useMemo Hook is changed. Take a look at the code snippet below.

We have a variable "tripledNumber" computed from an expensive function( We simulated an expensive function with a very for loop) which triples whatever number passed to it. In this case, we pass in our "count" state and every time we increment it the "tripledNumber" is also computed from the updated value.
We also have "name" state and we toggle between two names using the "Toggle names" button, but every time we toggle between those names, our App component is re-rendered and the "tripledNumber" is computed again every time. This makes toggling between those names very slow because of the slow expensive function being called every time the "tripledNumber". While we may be okay with the process being slow when we increment our count "state", the "name" toggler has nothing to do with our tripled number and it shouldn't be a slow process.
With useMemo we solve this problem easily, we wrap the function call of the "tripledNumber" variable with a useMemo and set the count state as a dependency.

Now with the useMemo hook, React caches the computed tripledNumber and doesn't re-calculate it unless the count state changes. This makes the toggle names button work fast because it is not re-calculating the "tripledNumber" each time the App component re-renders.

The useCallBack hook

The useCallBack is similar to the useMemo Hook, the key difference is that useMemo returns memoized value and useCallBack returns a memoized callback function. Let's walk through some examples.

Now this is similar to a problem we had earlier solved with the useMemo hook. We have a useEffect hook with a function as its dependency, a function is a nonprimitive value just like objects and arrays. Therefore the same problem we experienced with the objects will arise, every time a re-render occurs the function is re-created. And unlike with primitive values such as strings and numbers, the new function while identical will not be the same as the previous one. We have a referential equality problem once again.

Therefore, the useEffect code will run every single time a re-render of the App component occurs, for example when we increment our "count" state.

We don't want that of course so we make use of the useCallBack hook.

With the useCallBack hook, React now caches the function so it isn't re-created with each re-render, so the useEffect code doesn't run on every re-render.

We can also use the useCallBack hook to maintain our memo when we pass a function to a memoed Component.

We have a MemoedSubComponent which has been wrapped with the memo HOC, we would expect that the component doesn't re-render when a state in the App component is changed however that is not the case. Once again we have a referential equality problem. Similar to a problem we had earlier when we passed an object as a prop to our MemoedSubComponent, the component re-renders when increment our "count" state.

This is because a function just like an object is a non-primitive type, therefore when React compares the previous props of the MemoedSubComponent to the new props it will return false because the new "sayHello" function wouldn't be the same as the previous "sayHello" function. But if we wrap our component with the useCallBack hook, our memo is maintained and the MemoedSubComponentdoes not re-render.

Cautions on using React memo, useMemo and useCallBack

Now as powerful as React memo and the hooks are in optimizing React applications, they can easily be misused and could even do more harm than good in your application. This is because React has to do some extra work during the initial render of our components to cache whatever values we want to memoize, and if we overuse React memo by applying them they aren't needed it could slow down our application's initial render. Avoid using React memo in these scenarios.

  1. Avoid using the useMemo hook on literal primitive values.
    Firstly, if there is no referential equality problem to solve, there is no point memoizing basic strings, booleans, or numbers because even after a state change causes a re-render, they will remain the same value and return true in the comparison of their new and updated values.
    Secondly, these values are not computed, they are fixed, they do not change, therefore there is no value in memoizing them.

  2. When dealing with computed values, only use useMemo for expensive calculations, do not use useMemo to cache results of simple arithmetic operations, concatenating strings or any other non-expensive calculations.

  3. When dealing with non-primitive values, only use the useMemo when they are the dependency of a useEffect or when they are the props of a component wrapped in the memo HOC. We've covered these use cases stated here. There is no reason to memoize objects for example if you aren't solving some kind of referential equality problem as in those two cases. The same goes for functions and the useCallBack hook, only memoize functions with useCallBack when we have a referential equality problem.