The Good Tech Companies - Memoization in React: Powerful Tool or Hidden Pitfall?

Episode Date: July 1, 2024

This 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)
Starting point is 00:00:00 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.
Starting point is 00:00:38 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.
Starting point is 00:01:17 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
Starting point is 00:01:57 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,
Starting point is 00:02:29 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
Starting point is 00:03:17 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
Starting point is 00:03:50 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,
Starting point is 00:04:28 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,
Starting point is 00:04:46 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.
Starting point is 00:05:29 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
Starting point is 00:06:05 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.
Starting point is 00:06:24 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,
Starting point is 00:07:02 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
Starting point is 00:07:51 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
Starting point is 00:08:37 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
Starting point is 00:09:25 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
Starting point is 00:10:03 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
Starting point is 00:10:41 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.
Starting point is 00:11:30 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
Starting point is 00:12:17 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.
Starting point is 00:12:56 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
Starting point is 00:13:29 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
Starting point is 00:14:05 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.

There aren't comments yet for this episode. Click on any sentence in the transcript to leave a comment.