From 50bf5fc9e0b4d0f55335cf7f732899d7466f2bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Necati=20=C3=96zmen?= Date: Thu, 19 Sep 2024 17:20:50 +0300 Subject: [PATCH] docs(blog): update react memo post (#6356) --- ...react-memo.md => 2024-09-19-react-memo.md} | 208 +++++++++++++++++- 1 file changed, 198 insertions(+), 10 deletions(-) rename documentation/blog/{2024-01-16-react-memo.md => 2024-09-19-react-memo.md} (72%) diff --git a/documentation/blog/2024-01-16-react-memo.md b/documentation/blog/2024-09-19-react-memo.md similarity index 72% rename from documentation/blog/2024-01-16-react-memo.md rename to documentation/blog/2024-09-19-react-memo.md index de272b33c2c1..358aa7c0372f 100644 --- a/documentation/blog/2024-01-16-react-memo.md +++ b/documentation/blog/2024-09-19-react-memo.md @@ -8,7 +8,7 @@ image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2022-09-13-react-memo hide_table_of_contents: false --- -**_This article was last updated on January 16, 2024 to reflect the latest changes to the React memo API and to provide a more detailed explanation of how React.memo() works._** +**This article was last updated on September 19, 2024, to add sections on Deep vs Shallow Comparison in Memoization, Profiling Components in DevTools, and Best Practices for Using `React.memo()`.** ## Introduction @@ -22,18 +22,14 @@ Steps we'll cover in this post: - [What is Memoization?](#what-is-memoization) - [Why Memoization in React?](#why-memoization-in-react) - - [Excessive Re-rendering Due to Ancestor Re-rendering](#excessive-re-rendering-due-to-ancestor-re-rendering) - - [Expensive Utilities](#expensive-utilities) - - [Passing Callbacks to Children](#passing-callbacks-to-children) - [Memoization in React](#memoization-in-react) - [About the React Memoization Series](#about-the-react-memoization-series) +- [Project Overview](#project-overview) - [Memoizing a Functional Component using `React.memo()`](#memoizing-a-functional-component-using-reactmemo) - - [What is `React.memo` ?](#what-is-reactmemo-) - - [React.memo() - How to Memoize Component Props](#reactmemo---how-to-memoize-component-props) - - [When to Use `React.memo`](#when-to-use-reactmemo) - - [When Not to Use `React.memo`](#when-not-to-use-reactmemo) - - [React.memo: Prop Comparison](#reactmemo-prop-comparison) - - [Using React Memo with Custom Comparators](#using-react-memo-with-custom-comparators) +- [Best Practices for Using `React.memo()`](#best-practices-for-using-reactmemo) +- [You can Profile Components in DevTools](#you-can-profile-components-in-devtools) +- [Bonus: Deep vs Shallow Comparison in Memoization](#bonus-deep-vs-shallow-comparison-in-memoization) +- [Live Example](#live-example) ## What is Memoization? @@ -356,6 +352,89 @@ When we click on the `Sign Out` button in the navbar, we can see in the console This is now because **React memo** caches the props passed to the component and checks for incoming changes. Notice the Boolean value of `signedIn` printed to the console. A change in `signedIn`'s state renews the memoization and a re-render of the component is triggered. +## Best Practices for Using `React.memo()` + +I wanted to share a few best practices for using `React.memo()` with some code examples. These can help improve the performance of our React components: + +### Use it for frequently re-rendered components + +If a component is being re-rendered unnecessarily due to its parent re-rendering, `React.memo()` can help. For example, if we have a component like this: + +```jsx +const Post = ({ title, content }) => { + console.log("Rendering Post component"); + return ( +
+

{title}

+

{content}

+
+ ); +}; + +export default Post; +``` + +We can prevent unnecessary re-renders by wrapping it with `React.memo()`: + +```jsx +const Post = React.memo(({ title, content }) => { + console.log("Rendering Post component"); + return ( +
+

{title}

+

{content}

+
+ ); +}); + +export default Post; +``` + +Now, this component will only re-render if its `title` or `content` props change. + +### Avoid overusing `React.memo()` + +It’s important not to use `React.memo()` everywhere. If a component’s props change frequently, memoization can add more overhead than improvement. Use it only when the props don't change often. + +### Shallow comparison only + +By default, `React.memo()` performs a shallow comparison of props. If you’re passing complex objects or arrays, you might need to write a custom comparison function. For example: + +```jsx +const Post = React.memo( + ({ title, content }) => { + console.log("Rendering Post component"); + return ( +
+

{title}

+

{content}

+
+ ); + }, + (prevProps, nextProps) => { + return prevProps.title === nextProps.title; // Custom comparison + }, +); +``` + +In this example, we are only checking if the `title` has changed. The component will only re-render if the `title` prop changes, even if `content` changes. + +### Don’t memoize static or rarely updated components + +If a component is static or doesn’t receive changing props, there’s no need to use `React.memo()`. For example, this component doesn’t benefit from memoization: + +```jsx +const Footer = () => { + return ; +}; + +// No need to memoize here +``` + +Memoization would add unnecessary complexity without improving performance. + +By following these best practices and using `React.memo()` in the right situations, we can optimize performance without adding extra overhead. + ### When to Use `React.memo` This is actually what we want. Because we don't want `` to re-render when we don't need it to, and we want to re-render it when we need it to. @@ -416,6 +495,115 @@ Here, we are omitting `signedIn` from the comparison by including only `post`. N This is because, our `customComparator` checks for equality of incoming values of only `post` and excludes `signedIn` from the comparison. +## You can Profile Components in DevTools + +I wanted to share some tips on **Profiling Components in DevTools** to help us identify and fix performance bottlenecks in our React app. + +**Opening the React Profiler** + +- Install the **React Developer Tools** extension for Chrome or Firefox. +- Open **DevTools** (`F12` or `Ctrl + Shift + I`), go to the **Profiler** tab, and click **"Record"** before interacting with the app. + +**Capturing Component Renders** + +Interact with the app while recording (e.g., clicking buttons, changing state). The Profiler will track which components re-render and how long each takes. + +**Analyzing Results** + +After recording, view the timeline to inspect: + +- **Render Time**: Time taken for each component render. +- **Render Reason**: Prop or state changes causing re-renders. + +**Optimizing Slow Renders** + +If a component (e.g., `Post`) is re-rendering unnecessarily, wrap it in `React.memo()` to prevent it from re-rendering when props haven't changed. + +**Highlight Updates** + +Enable **"Highlight Updates"** in the **React** tab to visually see components that re-render, making it easier to spot unnecessary updates. + +Using the Profiler, we can quickly identify and optimize slow re-renders. + +## Bonus: Deep vs Shallow Comparison in Memoization + +I wanted to go over deep vs shallow comparison in memoization and how that affects performance optimization in React, especially when using `React.memo()` and other memoization techniques. + +### Shallow Comparison + +By default, React does **shallow comparison** to check if a component’s props have changed to decide whether to re-render it. Shallow comparison means React checks for changes only at the top level of an object or array and doesn't go into nested properties. + +For example: + +```jsx +const person1 = { name: "John" }; +const person2 = { name: "John" }; + +console.log(person1 === person2); // false - because of different object references +``` + +Even though `person1` and `person2` have the same data, during a shallow comparison, they are considered different because the reference is compared, not the content. + +```jsx +const Post = React.memo(({ title, content }) => { + console.log("Rendering Post component"); + return ( +
+

{title}

+

{content}

+
+ ); +}); +``` + +If the `title` and `content` props are **primitives** (like strings or numbers), the shallow comparison works as expected. But if they are **objects** or **arrays**, even a slight change in the reference (e.g., creating a new object) will trigger a re-render. + +### Deep Comparison + +A deep comparison goes beyond top-level properties and checks all nested properties. It's a bit heavier since every level of the object or array has to be checked. + +React doesn't perform deep comparisons by default in `React.memo()` because it can be slow, especially with deeply nested objects or large arrays. + +### Custom Comparators for Deep Comparison + +If shallow comparison isn’t enough (for example, when passing complex objects as props), we can provide a custom comparator function in `React.memo()` to implement **deep comparison**. + +Here’s a custom comparator for deep comparison: + +```jsx +import React from "react"; +import { isEqual } from "lodash"; + +const Post = ({ post }) => { + console.log("Rendering Post component"); + return ( +
+

{post.title}

+

{post.content}

+
+ ); +}; + +const customComparator = (prevProps, nextProps) => { + return isEqual(prevProps.post, nextProps.post); // Deep comparison using lodash +}; + +export default React.memo(Post, customComparator); +``` + +Here, we’re using `lodash`'s `isEqual()` function to deep compare the entire `post` object. This helps avoid unnecessary re-renders when only the reference changes, but the data inside remains the same. + +### When to Use Deep Comparison + +- **Complex Data Structures**: When passing large or deeply nested objects as props, and you don’t want to re-render components unnecessarily. +- **Performance Trade-offs**: Deep comparison might be slower than a shallow comparison, so it’s important to measure and ensure that the performance gain from avoiding re-renders outweighs the cost of deep comparison. + +### Best Practices + +- Use shallow comparison whenever possible to keep performance high. +- Only use deep comparison when you’re sure the props involve deeply nested objects that don’t change often. +- Use libraries like `lodash` or `deep-equal` for effective deep comparisons. + ## Summary In this post, we acknowledged what memoization is and why it is important in React. We learned about the use of `React.memo()`, `useMemo` and `useCallback` APIs for implementing memoization in a React app.