{"id":1487,"date":"2024-03-18T12:23:38","date_gmt":"2024-03-18T15:23:38","guid":{"rendered":"https:\/\/benjaminray.com\/codebase\/?p=1487"},"modified":"2024-08-17T19:31:58","modified_gmt":"2024-08-17T22:31:58","slug":"memoize-react-components-to-prevent-unnecessary-renders","status":"publish","type":"post","link":"https:\/\/benjaminray.com\/codebase\/memoize-react-components-to-prevent-unnecessary-renders\/","title":{"rendered":"Memoize React Components to Prevent Unnecessary Renders"},"content":{"rendered":"<p>When the state of a React component changes, the component and all of its children will re-render. While this is fine for most cases, you may have a child component that does some heavy computation, and you may want it only to render when its state or properties have changed, not every time the parent has changed. This is where component memoization comes in handy.<\/p>\n<p>You can easily memoize a component by wrapping the component in memo(). You can either export a memoized version of an existing component, or you can make the component always memoized. Examples of both are below.<\/p>\n<blockquote>\n<p>Heads up: memo() is different from useMemo(). useMemo() is for storing values between renders, not components.<\/p>\n<\/blockquote>\n<p>This child component will render every time the parent's state changes:<\/p>\n<pre><code class=\"language-jsx\">\/\/ Parent component:\nimport { ChildComponent } from &quot;.\/ChildComponent&quot;;\nexport const ParentComponent = ({ parentProp1 }) =&gt; {\n  console.log(&quot;ParentComponent last rendered at:&quot;, new Date().toLocaleTimeString());\n  return (\n    &lt;div&gt;\n      Amazing parent content with props {parentProp1}\n      &lt;ChildComponent prop1=&quot;test 1&quot; prop2=&quot;test 2&quot; \/&gt;\n    &lt;\/div&gt;\n  );\n};\n\n\n\/\/Non-memoized child component:\nexport const ChildComponent = ({ prop1, prop2 }) =&gt; {\n  console.log(&quot;ChildComponent last rendered at:&quot;, new Date().toLocaleTimeString());\n  return (\n    Amazing child component content with props {prop1} and {prop2}\n  );\n};\n<\/code><\/pre>\n<h2>Option 1 - Export a separate memoized version of the component:<\/h2>\n<pre><code class=\"language-jsx\">\/\/ Non-memoized child component with a memoized export:\nimport { memo } from &quot;react&quot;;\nexport const ChildComponent = ({ prop1, prop2 }) =&gt; {\n  console.log(&quot;ChildComponent last rendered at:&quot;, new Date().toLocaleTimeString());\n  return (\n    Amazing child component content with props {prop1} and {prop2}\n  );\n};\nexport const MemoizedChildComponent = memo(ChildComponent);\n\n\/\/ Use the memoized version of the child component:\n&lt;MemoizedChildComponent prop1=&quot;test 1&quot; prop2=&quot;test 2&quot; \/&gt;\n\n\/\/ Use the original, non-memoized version of the child component:\n&lt;ChildComponent prop1=&quot;test 1&quot; prop2=&quot;test 2&quot; \/&gt;\n\n<\/code><\/pre>\n<p>Your parent component can now reference either the memoized version using <code>&lt;MemoizedChildComponent \/&gt;<\/code>, or the non-memoized version using <code>&lt;ChildComponent \/&gt;<\/code>. This is useful<br \/>\nif you need both options.<\/p>\n<h2>Option 2 - Make the component always memoized:<\/h2>\n<pre><code class=\"language-jsx\">\/\/ Memoized child component\nimport { memo } from &quot;react&quot;;\nexport const ChildComponent = memo(function ChildComponent({ prop1, prop2 }) {\n  console.log(&quot;ChildComponent last rendered at:&quot;, new Date().toLocaleTimeString());\n  return (\n    Amazing child component content with props {prop1} and {prop2}\n  );\n});\n<\/code><\/pre>\n<p>Your parent component will continue to reference the memoized version using <code>&lt;ChildComponent \/&gt;<\/code>.<\/p>\n<h2>Callback Functions as Properties Break Memoization Without useCallback()<\/h2>\n<p>If you are passing a callback function as a property to the child, memoizing the component is not enough to avoided the extra renders. The callback function will appear by the child to be different every time the parent's state changes, which will trigger the child to re-render every time the parent re-renders. To fix this, you can memoize the function in the parent component using useCallback(), and pass the output of useCallback() as a property to the child. That will allow the memoized child component to render as expected: only when child properties (including callback functions) have actually changed.<\/p>\n<pre><code class=\"language-jsx\">\/\/ Memoized child component with callback function as parameter\nimport { memo } from &quot;react&quot;;\nexport const ChildComponent = memo(function ChildComponent({prop1, onClickCallback}) {\n  console.log(&quot;ChildComponent last rendered at:&quot;, new Date().toLocaleTimeString());\n  return (\n    &lt;div&gt;\n      Amazing child component content with props {prop1} and a callback function\n      &lt;button onClick={() =&gt; onClickCallback()}&gt;click me to notify the parent&lt;\/button&gt;\n    &lt;\/div&gt;\n  );\n});\n\n\/\/ Revised parent component using useCallback() to memoize a callback function sent to the child\nimport { useCallback } from &quot;react&quot;;\nimport { ChildComponent} from &quot;.\/ChildComponent&quot;;\nexport const ParentComponent = ({ parentProp1 }) =&gt; {\n  console.log(&quot;ParentComponent last rendered at:&quot;, new Date().toLocaleTimeString());\n  const childComponentOnClick = useCallback(() =&gt; console.log(&quot;Child component click callback in parent.&quot;), []);\n  return (\n    &lt;div&gt;\n      Amazing parent content with props {parentProp1}\n      &lt;ChildComponent prop1=&quot;test 1&quot; onClickCallback={childComponentOnClick} \/&gt;\n    &lt;\/div&gt;\n  );\n};\n<\/code><\/pre>\n<h2>Can you just memoize the child properties in the parent instead?<\/h2>\n<p>You might think that memoizing the properties for the child component in the parent component would possibly prevent the child from re-rendering. However, since a child component that accepts <em>no<\/em> properties will always render when the parent renders, passing down memoized properties to the child won't stop that behaviour. So no, memozing the properties in the parent before passing to the child won't stop the extra child renders.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>When the state of a React component changes, the component and all of its children will re-render. While this is fine for most cases, you may have a child component  [&#8230;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[28],"tags":[38,36,30],"class_list":["post-1487","post","type-post","status-publish","format-standard","hentry","category-react","tag-component","tag-memo","tag-react"],"acf":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p9GNjN-nZ","jetpack_likes_enabled":false,"_links":{"self":[{"href":"https:\/\/benjaminray.com\/codebase\/wp-json\/wp\/v2\/posts\/1487","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/benjaminray.com\/codebase\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/benjaminray.com\/codebase\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/benjaminray.com\/codebase\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/benjaminray.com\/codebase\/wp-json\/wp\/v2\/comments?post=1487"}],"version-history":[{"count":24,"href":"https:\/\/benjaminray.com\/codebase\/wp-json\/wp\/v2\/posts\/1487\/revisions"}],"predecessor-version":[{"id":2531,"href":"https:\/\/benjaminray.com\/codebase\/wp-json\/wp\/v2\/posts\/1487\/revisions\/2531"}],"wp:attachment":[{"href":"https:\/\/benjaminray.com\/codebase\/wp-json\/wp\/v2\/media?parent=1487"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/benjaminray.com\/codebase\/wp-json\/wp\/v2\/categories?post=1487"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/benjaminray.com\/codebase\/wp-json\/wp\/v2\/tags?post=1487"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}