How to memorize functions and values ​​in JavaScript and react

Memorization 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 runs. It is especially useful in compute-intensive applications that repeat function calls on the same parameters.

You can use memorization in raw JavaScript and also in React, in different ways.


Memoization in JavaScript

To memorize a function in JavaScript, you must store the results of this 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 present in the cache before executing. If so, it returns the cached results. Otherwise, it runs.

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() will work quite quickly. However, if you were to calculate the square of 70,000, there would be a noticeable lag. Not by much, but still a delay. Now, if you were to call the function multiple times and go over 70,000, you would experience a delay on each call.

You can eliminate this delay by using memorization.

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 if it calculated the result before, by checking if it exists in the cache object. If so, it returns the already calculated value.

When the function receives a new number, it calculates a new value and stores the results in the cache before returning.

Again, this example is quite simple, but it explains how memorization would work to improve the performance of a program.

You should only memorize pure functions. These functions return the same result when you pass the same arguments. If you use memorization on impure functions, you won’t improve performance but will increase your overhead. This is because you are choosing speed over memory each time you memorize a function.

Memoization in React

If you’re looking to optimize React components, React provides memorization through the useMemo() hook, React.memo, and useCallBack().

Using useMemo()

useMemo() is a React hook that accepts a function and an array of dependencies.

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

It stores the value returned by this function. The values ​​in the dependency array dictate when the function is executed. It is only when they change that the function is executed again.

For example, the following App component has a stored value called 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() each time it renders. Performance will degrade if the App component is rendered multiple times due to changing React props or updating state, especially if the square() function is expensive.

However, since useMemo() caches the returned values, the square function is not executed on each new render unless the dependency array arguments change.

Using React.memo()

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

The function is optional and if not provided, React.memo performs a shallow copy comparison of the component’s current props with its previous props. If the props are different, it triggers an update. If the props are the same, it ignores the new rendering and reuses the stored values.

The optional function accepts the preceding props and the following 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)])

Let’s first look at an example without the optional function argument. Below is a component called Comments that accepts 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 will be surrounded by React.memo like this:

const MemoizedComment = React.memo(Comment)

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

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

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

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 is returned.

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

Unlike the useMemo() hook which remembers only the return value of a function, React.memo remembers the whole function.

Use React.memo only for pure components. Also, to reduce comparison costs, remember only components whose accessories change often.

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 remembers the function component between renders instead of remembering the values.

Consider the following example of a stored 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 will only be called again when the getData value changes.

Should I memorize?

In this tutorial, you learned what memorization is, its benefits, and how to implement it in JavaScript and React. However, you should know that React is already fast. In most cases, remembering components or values ​​increases comparison costs and does not improve performance. For this reason, memorize only expensive components.

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

Comments are closed.