The Good Tech Companies - Memoization in React: Powerful Tool or Hidden Pitfall?
Episode Date: July 1, 2024This story was originally published on HackerNoon at: https://hackernoon.com/memoization-in-react-powerful-tool-or-hidden-pitfall. Discover how overusing memoization in ...React apps can lead to performance issues. Learn where it fails and how to avoid these hidden traps in your development. Check more stories related to programming at: https://hackernoon.com/c/programming. You can also check exclusive content about #react, #memoization, #memorization-techniques, #react-tutorial, #react-app, #react-components, #react-development, #good-company, and more. This story was written by: @socialdiscoverygroup. Learn more about this writer by checking @socialdiscoverygroup's about page, and for more stories, please visit hackernoon.com. A widespread approach in React application development is to cover everything with memorization. The Social Discovery Group team discovered how overusing memoization in React apps can lead to performance issues. Learn where it fails and how to avoid these hidden traps in your development.
Transcript
Discussion (0)
This audio is presented by Hacker Noon, where anyone can learn anything about any technology.
Memoization in React. Powerful tool or hidden pitfall, by Social Discovery Group.
A widespread approach in React application development is to cover everything with
memoization. Many developers apply this optimization technique liberally,
wrapping components in memoization to prevent unnecessary re-renders.
On the surface, it seems like a foolproof strategy to enhance performance.
However, the Social Discovery Group team has discovered that when misapplied,
memoization can actually break in unexpected ways, leading to performance issues.
In this article, we'll explore the surprising places where memoization can falter and how
to avoid these hidden traps in your React applications. And what is memoization can falter and how to avoid these hidden traps in your react applications
and what is memoization memoization is an optimization technique in programming that
involves saving the results of expensive operations and reusing them when the same
inputs are encountered again the essence of memoization is to avoid redundant computations
for the same input data this description is indeed true for traditional
memoization. You can see in the code example that all computations are cached. N-memoization
offers several benefits, including performance improvement, resource savings, and result caching.
However, React's memoization works until new props come in, meaning only the result of the
last call is saved. Memoization tools in React
The React library provides us with several tools for memoization. These are the HOC React. Memo,
the hooks useCallback, useMemo, and useEvent, as well as React. Pure component and the lifecycle
method of class components should component update. Let's examine the first three memoization
tools and explore their purposes
and usage in React. React memo this higher order component, hock, accepts a component as its first
argument and an optional comparison function as its second. The comparison function allows for
manual comparison of previous and current props. When no comparison function is provided, React
defaults to shallow equality.
It's crucial to understand that shallow equality only performs a surface-level comparison.
Consequently, if the props contain reference types with non-constant references,
React will trigger a render of the component.
React useCallbackThe useCallback hook allows us to preserve the reference to a function
passed during the initial render.
On subsequent renders, React will compare the values in the hook's dependency array,
and if none of the dependencies have changed, it will return the same cached function reference
as last time. In other words, use callback caches the reference to the function between
renders until its dependencies change. React use memo the use memo hook allows you to cache the result of a
computation between renders. Typically use memo is used to cache expensive computations as well
as to store a reference to an object when passing it to other components wrapped in the memo hawk
or as a dependency in hooks. N full m e m o i z a t i o n of a project in React development teams, a widespread practice is comprehensive
memoization. This approach typically involves wrapping all components in React. Memo, using
use callback for all functions passed to other components, caching computations and reference
types with use memo. However, developers don't always grasp the intricacies of this strategy
and how easily memoization can be
compromised. It's not uncommon for components wrapped in the memo hawk to unexpectedly re-render.
At Social Discovery Group, we've heard colleagues claim,
memoizing everything isn't much more expensive than not memoizing at all.
We've noticed that not everyone fully grasps a crucial aspect of memoization.
It works optimally without additional tweaks
only when primitives are passed to the memoized component. 1. In such cases, the component will
re-render only if the prop values have actually changed. Primitives in props are good. 2. The
second point is when we pass reference types in props. It is important to remember and understand
that there is no magic in React. It is a JavaScript library that operates according to the rules of JavaScript.
Reference types in props, functions, objects, arrays, are dangerous.
For example, n if you create an object, another object with the same properties and values is
not equal to the first one because they have different references.
If we pass what seems to be the same object on a subsequent call of the component,
but it is actually a different one,
since its reference is different,
the shallow comparison that React uses
will recognize these objects as different.
This will trigger a re-render of the component
wrapped in memo, thus breaking the memoization
of that component.
To ensure safe operation with memoized components,
it's important to use a combination of memo,
use callback, and use memo. This way, all reference types will have constant references.
Teamwork. Memo, use callback, use memo. Let's break the memo, shall we? Everything described
above sounds logical and simple, but let's take a look together at the most common mistakes that
can be made when working with this approach. Some of them might be subtle, and some might be a bit of a stretch, but we need to be
aware of them and, most importantly, understand them to ensure that the logic we implement with
full memoization doesn't break. Inlining to memoization let's start with a classic mistake,
where on each subsequent render of the parent component, the memoized component memo component
will constantly re-render because the reference to the object passed in params will always be new.
And to solve this problem, it's sufficient to use the previously mentioned use memo hook.
Now, the reference to our object will always be constant. Alternatively, you can move this
into a constant outside the component if the array always contains static data.
A similar situation in this example
is passing a function without memoization. In this case, like in the previous example,
memoization of the memo component will be broken by passing a function to it that will have a new
reference 1 a render of the parent component. Consequently, memo component will render a new
as if it were not memorized. And here, we can use the useCallback hook
to preserve the reference to the past function
between renders of the parent component.
End note taken.
Also, in useCallback, there is nothing preventing you
from passing a function that returns another function.
However, it's important to remember that in this approach,
the function backquote sum function backquote
will be called on every render.
It's crucial to avoid complex calculations inside backquote sum function backquote some function backquote will be called on every render. It's crucial to avoid complex calculations inside backquote some function backquote.
End props SPR eating the next common situation is props spreading.
Imagine you have a chain of components. How often do you consider how far the data prop
passed from initial component can travel, potentially being unnecessary for some
components in this chain. In this example, this prop will break memoization in the child memo component because,
on each render of the initial component, its value will always change.
In a real project, where the chain of memoized components can be long,
all memoization will be broken because unnecessary props with constantly changing values are passed to them,
and to safeguard yourself, ensure that only the necessary values are passed to the memoized component. Instead of that use pass only the
necessary props memo and children let's consider the following example. A familiar situation is
when we write a component that accepts JSX as children and at first glance it seems harmless
but in reality it's not. Let's take a closer look at the code where we pass JSX as children to a memoized component.
This syntax is nothing but syntactic sugar for passing this backquote div backquote as a prop named backquote children backquote.
Children are no different from any other prop we pass to a component.
In our case, we are passing JSX, and JSX, in turn, is syntactic sugar for the
backquote create element backquote method, so essentially, we are passing a regular object
with the type backquote div backquote. And here, the usual rule for a memoized component applies.
If a non-memoized object is passed in the props, the component will be re-rendered when its parent
is rendered, as each time the reference to this object will menu. The solution to such a problem was discussed a few abstracts earlier, in the
block regarding the passing of a non-memoized object. So here, the content being passed can
be memoized using useMemo, and passing it as children to childMemo will not break the
memoization of this component. N-parent memo and child memo is consider a more interesting
example. N at first glance, it seems harmless. We have two components, both of which are memorized.
However, in this example, parent memo will behave as if it's not wrapped in memo because its
children, child memo, are not memorized. The result of the child memo component will be JSX, and JSX is just syntactic sugar for React.
Create element, which returns an object. So, after the React, create element method executes,
parent memo and child memo will become regular JavaScript objects, and these objects are not
memoized in any way. Parent memo child memo, and as a result, we pass a non-memoized object to the props,
thereby breaking the memoization of the parent component. To address this issue,
it's sufficient to memoize the past child, ensuring its reference remains constant during
the render of the parent app component. And non-primitives from custom hooks
Another dangerous and implicit area is custom hooks. Custom hooks help us extract
logic from our components, making the code more readable and hiding complex logic. However,
they also hide from us whether their data and functions have constant references.
In my example, the implementation of the submit function is hidden in the custom hook use form,
and on each render of the parent component, the hooks will be re-executed.
And can we understand from the code whether it's safe to pass the submit method as a prop to the
memoized component component memo? Of course not, and in the worst case, the implementation of the
custom hook might look like this, and by passing the submit method into the memoized component,
we will break the memoization because the reference to the submit method will be new
with each render of the parent component. To solve this problem, you can use the use
callback hook. But the main point I wanted to emphasize is that you shouldn't blindly use data
from custom hooks to pass them into memoized components if you don't want to break the
memoization you've implemented. When is memoization excessive? Even if you cover everything with
memoization, Like any approach,
full memoization should be used thoughtfully, and one should strive to avoid blatantly excessive
memoization. Let's consider the following example in this example. It's tempting to wrap the handle
change method in use callback because passing handle change in its current form will break
the memoization of the input component since the reference to handle change will always be new. However, when the state changes, the input component will be
re-rendered anyway because a new value will be passed to it in the value prop. So, the fact that
we didn't wrap handle change in useCallback won't prevent the input component from constantly
re-rendering. In this case, using useCallback would be excessive. Next, I would like to provide a few
examples of real code seen during code reviews. And considering how simple the operation of adding
two numbers or concatenating strings is, and that we get primitives as output in these examples,
it's obvious that using use memo here makes no sense. Similar examples here, and based on the
dependencies in use memo, different results can be obtained, but again, they are primitives and there are no complex calculations inside.
Executing any of these conditions on each render of the component is cheaper than using useMemo.
Conclusions 1. Optimization, not always beneficial. Performance optimizations are not free, and the cost of these optimizations may not always be commensurate with the benefits you will gain from them.
N asterisk 2. Measure the results of optimizations.
If you don't measure, you can't know whether your optimizations have improved anything or not.
And most importantly, without measurements, you won't know if they made things worse.
N asterisk 3. Measure the effectiveness of memorization. Whether to use full memoization or
not can only be understood by measuring how it performs in your specific case. Memoization is
not free when caching or memoizing computations, and this can affect how quickly your application
starts up for the first time and how quickly users can start using it. For example, if you want to
memoize some complex calculation whose result
needs to be sent to the server when a button is pressed, should you memoize it when your
application starts up? Maybe not, because there's a chance the user will never press that button,
and executing that complex calculation might be unnecessary altogether.
N asterisk 4. Think first, then memorize. Asterisk Memoizing props passed to a component only makes sense if
it is wrapped in backquote memo backquote, or if the received props are used in the dependencies
of hooks, and also if these props are passed to other memoized components. N asterisk.
5. Remember the basic principles of JavaScript. While working with React or any other library
and framework, it's important not to forget that all of this is implemented in JavaScript and operates according to the rules of this
language.
And what tools can be used for measurement?
We can recommend at least four of these tools for measuring the performance of your application
code.
React, Profiler with React, Profiler, you can wrap either the specific component you
need or the entire application to obtain information about the time of the initial and subsequent renders. You can also understand in which exact phase the
metric was taken. React Developer Tools
React Developer Tools is a browser extension that allows you to inspect the component hierarchy,
track changes in states and props, and analyze the performance of the application.
React Render Tracker Another interesting tool is React Render
Tracker, which helps detect potentially unnecessary re-renders when props in non-memoized components
do not change or change to similar ones. Storybook with the Storybook Add-on Performance Add-on
Also, in Storybook, you can install an interesting plugin called Storybook Add-on Performance from
Atlassian. With this plugin, you can run tests to obtain
information about the speed of initial render, re-render, and server-side rendering. These tests
can be run for multiple copies as well as multiple runs simultaneously, minimizing testing inaccuracies.
Written by Sergey Levkovic, Senior Software Engineer at Social Discovery Group,
NNNNN. Thank you for listening to this Hackernoon story,
read by Artificial Intelligence. Visit hackernoon.com to read, write, learn and publish.