How to Memoize Functions and Values in JavaScript and React

Memoization is an optimization technique, similar to caching. It works by storing the previous results of a function call and using those results the next time the function is run. It’s especially useful in computationally intensive apps that repeat function calls with the same parameters.

You can use memoization in a variety of ways in plain JavaScript and also in React.


Memos in JavaScript

To store a function in JavaScript, you must store the results of that function in a cache. The cache can be an object with the arguments as keys and the results as values.

When you call this function, it first checks if the result is in the cache before executing it. If so, the cached results are returned. Otherwise it will be executed.

Consider this function:

function square(num) {
return num * num
}

The function takes an argument and returns its square.

To run the function, call it with a number like this:

square(5) 

With 5 as an argument, square() runs pretty fast. However, if you calculated the square of 70,000, there would be a noticeable lag. Not much, but still a delay. Now if you called the function multiple times and passed 70,000, you would notice a delay with each call.

You can eliminate this delay with memoization.

const memoizedSquare = () => {
let cache = {};
return (num) => {
if (num in cache) {
console.log('Reusing cached value');
return cache[num];
} else {
console.log('Calculating result');
let result = num * num;

// cache the new result value for next time
cache[num] = result;
return result;
}
}
}

In this example, the function checks whether it has previously calculated the result by checking whether it is present in the cache object. If so, the value already calculated is returned.

When the function gets a new number, it calculates a new value and caches the results before returning.

Again, this example is fairly simple, but it explains how memoization would work to improve a program’s performance.

You should only memorize pure functions. These functions return the same result when you pass the same arguments. If you use memoization for impure functions, you don’t improve performance, you increase your overhead. That’s because every time you memorize a feature, you choose speed over memory.

Memorizing in React

If you want to tweak React components, React offers memoization via the useMemo(), React.memo, and useCallBack() hook.

Using useMemo()

useMemo() is a React hook that accepts a function and a dependency array.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

It stores the value returned by this function. The values ​​in the dependency array determine when the function runs. Only when they change is the function executed again.

For example, the following app component has a stored value named result.

import { useMemo } from "react"
function App(value) {
const square = (value) => {
return value * value
}
const result = useMemo(
() => square(value),
[ value ]
);
return (
<div>{result(5)}</div>
)
}

The app component calls square() on each render. Performance degrades when the app component is rendered many times due to React props changing or status updates, especially when the square() function is expensive.

However, because useMemo() caches the returned values, the quadratic function will not be executed on each replay unless the arguments in the dependency array change.

Using React.memo()

React.memo() is a high-order component that takes a react component and a function as arguments. The function determines when the component should be updated.

The feature is optional, and if not provided, React.memo will perform a shallow copy comparison of the component’s current props to its previous props. If the props are different, an update will be triggered. If the props are the same, the re-rendering is skipped and the stored values ​​are reused.

The optional function accepts the previous props and the next props as arguments. You can then explicitly compare these props to decide whether or not to update the component.

React.memo(Component, [areEqual(prevProps, nextProps)])

First, let’s look at an example without the optional function argument. Below is a component called Comments that accepts the name and email props.

function Comments ({name, comment, likes}) {
return (
<div>
<p>{name}</p>
<p>{comment}</p>
<p>{likes}</p>
</div>
)
}

The memorized comments component is wrapped by React.memo as follows:

const MemoizedComment = React.memo(Comment)

You can call it like any other React component and then invoke it.

<MemoizedComment name="Mary" comment="Memoization is great" likes=1/>

If you want to do the props comparison yourself, pass the following function as the second argument to React.memo.

import React from "react"
function checkCommentProps(prevProps, nextProps) {
return prevProps.name === nextProps.name
&& prevProps.comment === nextProps.comment
&& prevProps.likes === nextProps.likes
}

const MemoizedComment = React.memo(Comments, checkCommentProps)

If checkProfileProps returns true, the component is not updated. Otherwise it will be rendered again.

The custom function is useful when you want to customize re-rendering. For example, you could use it to update the Comments component only when the number of likes changes.

Unlike the useMemo() hook, which only stores the returned value of a function, React.memo stores the entire function.

Only use React.memo for pure components. In addition, to reduce the comparison cost, remember only components whose props change frequently.

Using useCallBack()

You can use the useCallBack() hook to remember function components.

const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);

The function is updated only when the values ​​in the dependency array change. The hook works like the useMemo() callback, but it saves the function component between renders instead of remembering values.

Consider the following example of a memorized function that calls an API.

import { useCallback, useEffect } from "react";
const Component = () => {
const getData = useCallback(() => {
console.log('call an API');
}, []);
useEffect(() => {
getData();
}, [getData]);
};

The getData() function called in useEffect is called again only when the getData value changes.

Should you memorize?

In this tutorial, you learned what memoization is, its benefits, and how to implement it in JavaScript and React. However, you should know that React is already fast. In most cases, memorizing components or values ​​adds comparison costs and does not improve performance. Therefore, remember only expensive components.

React 18 also introduced new hooks like useId, useTransition and useInsertionEffect. You can use these to improve the performance and user experience of React applications.

Leave a Reply

Your email address will not be published. Required fields are marked *