Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cx as a dependency of useMemo across @wordpress/components package #38541

Merged
merged 6 commits into from
Feb 7, 2022

Conversation

ciampo
Copy link
Contributor

@ciampo ciampo commented Feb 4, 2022

Description

Explanation of the issue

While investigating the issue flagged in #35619 (comment) in #38447 , @sarayourfriend and I managed to reproduce the issue in a Storybook example:

  • a component using Emotion is being rendered both inside and outside of an iframe, but styles are apparently only applied correctly to the outside element
  • that component would use the useCx hook to compute the classname, and would have that computation wrapped inside React's useMemo hook. But the cx function would not be part of the useMemo dependencies

The combination of the 2 points above causes the component's classnames to be computed only once for the component outside of the iframe. The component inside the iframe gets the same classnames (because of the memoization), but those classnames can't be found inside the iframe (since the styles are added to the iframe via a different StyleProvider).

Long story short: when rendered inside the iframe, the component's cx function updates to reference the correct Emotion cache, but the component doesn't get the correct classnames because, when the cx function updates, the memoized classnames remains the same.

What's included in the PR:

This PR, therefore:

  • Updates all usages of cx and useMemo in the @wordpress/components package, adding cx to the list of dependencies
  • Adds a Storybook example showcasing this anti-pattern

What should we do next

As also discussed with @sarayourfriend , we should assess if the usage of useMemo when computing Emotion's classnames is actually necessary to optimise the rendering performance of components.

We also wondered why eslint didn't flag the missing cx dependency — it looks like we're not enforcing the exhaustive-deps rule, I wonder what's the reason for it? (cc @gziolo )

Finally, in case we have good reasons not to enforce the exhaustive-deps rule, and if we decided to keep memoizing Emotion classname's computation, we could consider adding our own eslint rule aimed specifically at making sure that cx is always added as a dependency of useMemo

Testing Instructions

  • Inspect the "Use Memo Bad Practices" Storybook example:
    • check that the text in the IFrame that should be blue, is not blue
    • check that, by adding cx to the list of dependencies of useMemo in the ExampleWithUseMemoWrong component, the color's becomes blue
  • Make sure that cx has been added as a dependency of useMemo in all of the instances where the 2 are used together in the @wordpress/components package

Screenshots

Screenshot 2022-02-04 at 23 04 41

Types of changes

Fix

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • I've tested my changes with keyboard and screen readers.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • N/A I've updated all React Native files affected by any refactorings/renamings in this PR (please manually search all *.native.js files for terms that need renaming or removal).
  • N/A I've updated related schemas if appropriate.

@ciampo ciampo force-pushed the fix/emotion-use-cx-use-memo branch from 6a98cf4 to e82ad27 Compare February 4, 2022 21:46
@github-actions
Copy link

github-actions bot commented Feb 4, 2022

Size Change: +32 B (0%)

Total Size: 1.14 MB

Filename Size Change
build/components/index.min.js 215 kB +32 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 960 B
build/admin-manifest/index.min.js 1.1 kB
build/annotations/index.min.js 2.75 kB
build/api-fetch/index.min.js 2.22 kB
build/autop/index.min.js 2.12 kB
build/blob/index.min.js 459 B
build/block-directory/index.min.js 6.28 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/index.min.js 141 kB
build/block-editor/style-rtl.css 14.6 kB
build/block-editor/style.css 14.6 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 470 B
build/block-library/blocks/button/editor.css 470 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 90 B
build/block-library/blocks/code/style.css 90 B
build/block-library/blocks/code/theme-rtl.css 131 B
build/block-library/blocks/code/theme.css 131 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-query-loop/editor-rtl.css 95 B
build/block-library/blocks/comments-query-loop/editor.css 95 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.4 kB
build/block-library/blocks/cover/style.css 1.4 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 322 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 965 B
build/block-library/blocks/gallery/editor.css 967 B
build/block-library/blocks/gallery/style-rtl.css 1.61 kB
build/block-library/blocks/gallery/style.css 1.61 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 159 B
build/block-library/blocks/group/editor.css 159 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 114 B
build/block-library/blocks/heading/style.css 114 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 810 B
build/block-library/blocks/image/editor.css 809 B
build/block-library/blocks/image/style-rtl.css 500 B
build/block-library/blocks/image/style.css 503 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 447 B
build/block-library/blocks/latest-posts/style.css 446 B
build/block-library/blocks/list/style-rtl.css 94 B
build/block-library/blocks/list/style.css 94 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 649 B
build/block-library/blocks/navigation-link/editor.css 650 B
build/block-library/blocks/navigation-link/style-rtl.css 94 B
build/block-library/blocks/navigation-link/style.css 94 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 343 B
build/block-library/blocks/navigation/editor-rtl.css 1.98 kB
build/block-library/blocks/navigation/editor.css 1.99 kB
build/block-library/blocks/navigation/style-rtl.css 1.85 kB
build/block-library/blocks/navigation/style.css 1.84 kB
build/block-library/blocks/navigation/view.min.js 2.81 kB
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 401 B
build/block-library/blocks/page-list/editor.css 402 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 273 B
build/block-library/blocks/paragraph/style.css 273 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/style-rtl.css 446 B
build/block-library/blocks/post-comments-form/style.css 446 B
build/block-library/blocks/post-comments/style-rtl.css 521 B
build/block-library/blocks/post-comments/style.css 521 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 323 B
build/block-library/blocks/post-template/style.css 323 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 389 B
build/block-library/blocks/pullquote/style.css 388 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 131 B
build/block-library/blocks/query/editor.css 132 B
build/block-library/blocks/quote/style-rtl.css 201 B
build/block-library/blocks/quote/style.css 201 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 99 B
build/block-library/blocks/separator/editor.css 99 B
build/block-library/blocks/separator/style-rtl.css 245 B
build/block-library/blocks/separator/style.css 245 B
build/block-library/blocks/separator/theme-rtl.css 172 B
build/block-library/blocks/separator/theme.css 172 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 744 B
build/block-library/blocks/site-logo/editor.css 744 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.37 kB
build/block-library/blocks/social-links/style.css 1.36 kB
build/block-library/blocks/spacer/editor-rtl.css 332 B
build/block-library/blocks/spacer/editor.css 332 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 471 B
build/block-library/blocks/table/editor.css 472 B
build/block-library/blocks/table/style-rtl.css 481 B
build/block-library/blocks/table/style.css 481 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 214 B
build/block-library/blocks/tag-cloud/style.css 215 B
build/block-library/blocks/template-part/editor-rtl.css 560 B
build/block-library/blocks/template-part/editor.css 559 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 921 B
build/block-library/common.css 919 B
build/block-library/editor-rtl.css 10.1 kB
build/block-library/editor.css 10.1 kB
build/block-library/index.min.js 167 kB
build/block-library/reset-rtl.css 474 B
build/block-library/reset.css 474 B
build/block-library/style-rtl.css 11.1 kB
build/block-library/style.css 11.1 kB
build/block-library/theme-rtl.css 672 B
build/block-library/theme.css 676 B
build/block-serialization-default-parser/index.min.js 1.09 kB
build/block-serialization-spec-parser/index.min.js 2.79 kB
build/blocks/index.min.js 46.4 kB
build/components/style-rtl.css 15.5 kB
build/components/style.css 15.5 kB
build/compose/index.min.js 11.2 kB
build/core-data/index.min.js 13.4 kB
build/customize-widgets/index.min.js 11.4 kB
build/customize-widgets/style-rtl.css 1.5 kB
build/customize-widgets/style.css 1.49 kB
build/data-controls/index.min.js 631 B
build/data/index.min.js 7.49 kB
build/date/index.min.js 31.9 kB
build/deprecated/index.min.js 485 B
build/dom-ready/index.min.js 304 B
build/dom/index.min.js 4.5 kB
build/edit-navigation/index.min.js 16.2 kB
build/edit-navigation/style-rtl.css 3.76 kB
build/edit-navigation/style.css 3.76 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/index.min.js 29.7 kB
build/edit-post/style-rtl.css 7.15 kB
build/edit-post/style.css 7.14 kB
build/edit-site/index.min.js 41.5 kB
build/edit-site/style-rtl.css 7.22 kB
build/edit-site/style.css 7.21 kB
build/edit-widgets/index.min.js 16.7 kB
build/edit-widgets/style-rtl.css 4.17 kB
build/edit-widgets/style.css 4.17 kB
build/editor/index.min.js 38.6 kB
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.71 kB
build/element/index.min.js 3.29 kB
build/escape-html/index.min.js 517 B
build/format-library/index.min.js 6.58 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.63 kB
build/html-entities/index.min.js 424 B
build/i18n/index.min.js 3.75 kB
build/is-shallow-equal/index.min.js 501 B
build/keyboard-shortcuts/index.min.js 1.8 kB
build/keycodes/index.min.js 1.39 kB
build/list-reusable-blocks/index.min.js 1.72 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.92 kB
build/notices/index.min.js 925 B
build/nux/index.min.js 2.08 kB
build/nux/style-rtl.css 747 B
build/nux/style.css 743 B
build/plugins/index.min.js 1.95 kB
build/primitives/index.min.js 917 B
build/priority-queue/index.min.js 582 B
build/react-i18n/index.min.js 671 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.65 kB
build/reusable-blocks/index.min.js 2.22 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11 kB
build/server-side-render/index.min.js 1.58 kB
build/shortcode/index.min.js 1.49 kB
build/token-list/index.min.js 639 B
build/url/index.min.js 1.9 kB
build/viewport/index.min.js 1.05 kB
build/warning/index.min.js 248 B
build/widgets/index.min.js 7.15 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.04 kB

compressed-size-action

@ciampo ciampo self-assigned this Feb 4, 2022
@ciampo ciampo added [Feature] Component System WordPress component system [Package] Components /packages/components [Type] Bug An existing feature does not function as intended labels Feb 4, 2022
@ciampo ciampo marked this pull request as ready for review February 4, 2022 22:14
@ciampo ciampo requested a review from ajitbohra as a code owner February 4, 2022 22:14
Copy link
Member

@mirka mirka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ that you added Storybook repros!

Agreed on all the next steps you listed. I'm especially concerned about the exhaustive-deps linter not being in place. There seems to have been some history around it (#24914 (comment)). I checked that rule against the current codebase, and there are already a lot of instances that are either bugs, or at the very least, unclear whether they are intentional (which I'd say is equally as troubling). I am a strong 👍 for adding this warn ASAP.

const ExampleWithUseMemoWrong = ( { args, children } ) => {
const cx = useCx();
// Wrong: using 'useMemo' without adding 'cx' to the dependency list.
const classes = useMemo( () => cx( ...args ), [ ...args ] );
Copy link
Member

@mirka mirka Feb 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming that we will be adding the exhaustive-deps rule soon (🤞🤞🤞), would it be worth adding a preemptive eslint-disable-next-line here?

Also noting that the linter will throw up this warning if we use spreads (...args):

React Hook useMemo has a spread element in its dependency array. This means we can't statically verify whether you've passed the correct dependencies. react-hooks/exhaustive-deps

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming that we will be adding the exhaustive-deps rule soon (🤞🤞🤞), would it be worth adding a preemptive eslint-disable-next-line here?

Makes sense, I've added it!

Also noting that the linter will throw up this warning if we use spreads (...args):

Good point. I rewrote the Storybook examples to avoid using the args array prop, and replace it with a serializedStyles prop

@sarayourfriend
Copy link
Contributor

sarayourfriend commented Feb 5, 2022

a component using Emotion is being rendered both inside and outside of an frame, but styles are apparently only applied correctly to the outside element

I think it would be applied to the first element that gets rendered (i.e., the first one that manages to call useMemo). Just wanted to mention this in case someone is able to reproduce the case where the styles are only applied to the one inside the frame.

I also wanted to mention that this will only effect instances of Emotion components using useMemo where the style props are all the same, that is, all the props passed to useMemo deps array. That's why potentially you could get multiple working instances of a component correctly styled, just as long as something used for classname calculation was different between the two.

that component would use the useCx hook to compute the classname, and would have that computation wrapped inside React's useMemo hook. But the cx function would not be part of the useMemo dependencies

I also wanted to throw in that the root cause of this is that the cx function is a byproduct of retrieving the emotion cache.

Also wanted to point out that useCx already uses useCallback to memoize the cx function creation. This means that useMemo will still cache classname generation for identical components within the same cache context. This is important because it does actually achieve a valid performance difference to just relying on Emotion's style serialization cache, one I hadn't considered before! That is, component instances that have identical styles will be able to completely avoid calling to cx altogether, and in many cases save several calls to the css function, saving potentially expensive string or (potentially worse) style object comparisons.

It would still be worth it to evaluate what difference useMemo is making in Gutenberg's Emotion styled components. There's probably a world where we more carefully use memoization... that is, we use it when appropriate for components/hooks that are used widely (for example useFlex is actually probably a very good use case for useMemo).

Looking at the instances in this PR where we've had to add cx as a dependency nothing stands out to me... but we should keep an eye out for components that aren't as widely used or when the styles are almost always going to be different or will change significantly over the lifetime of the component... especially the latter case as then we'll be trading non-trivial memory usage (over the lifetime of a long writing session for example) for no performance gain at all.

Also, when we discovered this @ciampo, it was a big TIL moment for me! I did not know that useMemo caches were shared across all instances of a component, I'd assumed it was per-instance. This nuance isn't even mentioned in the documentation as far as I can tell, but is a pretty interesting one! I can imagine it's the correct choice 99% of the time, but in some cases will bite you in the butt.

For example, here I think we didn't include cx in the dependency array potentially because we thought that it would have at least called the cx function relevant to the component's current EmotionCache context on the first calculation of the memoized value per component. Or at least, that's definitely what I would have expected to happen before we discovered what was wrong!

Scratch that! I have no idea why that was the behavior we were seeing in Gutenberg unless it's React version dependent. I am not able to reproduce that behavior in codesandbox: https://codesandbox.io/s/broken-frost-643dz?file=/src/App.js

Copy link
Contributor

@sarayourfriend sarayourfriend left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonderful! I'm so glad we were able to hunt down what was causing this issue. It'd been plaguing me for a couple weeks now 🙂

@ciampo ciampo force-pushed the fix/emotion-use-cx-use-memo branch from 1a91f41 to 7366a53 Compare February 7, 2022 09:33
@youknowriad
Copy link
Contributor

nice work catching that 👍

@ciampo
Copy link
Contributor Author

ciampo commented Feb 7, 2022

Thank you @sarayourfriend and @mirka for you reviews!

Agreed on all the next steps you listed. I'm especially concerned about the exhaustive-deps linter not being in place. [...] I am a strong 👍 for adding this warn ASAP.

I've added a comment to that PR asking for its status.

Also wanted to point out that useCx already uses useCallback to memoize the cx function creation. This means that useMemo will still cache classname generation for identical components within the same cache context. This is important because it does actually achieve a valid performance difference to just relying on Emotion's style serialization cache, one I hadn't considered before! That is, component instances that have identical styles will be able to completely avoid calling to cx altogether, and in many cases save several calls to the css function, saving potentially expensive string or (potentially worse) style object comparisons.

This is also something that I hadn't realised so far, thank you for pointing it out!

It would still be worth it to evaluate what difference useMemo is making in Gutenberg's Emotion styled components. There's probably a world where we more carefully use memoization... that is, we use it when appropriate for components/hooks that are used widely (for example useFlex is actually probably a very good use case for useMemo).

Absolutely! Do you know if we conducted a similar test in the past? In that case we could replicate the same setup

Scratch that! I have no idea why that was the behavior we were seeing in Gutenberg unless it's React version dependent. I am not able to reproduce that behavior in codesandbox: https://codesandbox.io/s/broken-frost-643dz?file=/src/App.js

Do you think that's something worth looking into?

@ciampo ciampo merged commit 6eff9a5 into trunk Feb 7, 2022
@ciampo ciampo deleted the fix/emotion-use-cx-use-memo branch February 7, 2022 11:49
@github-actions github-actions bot added this to the Gutenberg 12.6 milestone Feb 7, 2022
@sarayourfriend
Copy link
Contributor

sarayourfriend commented Feb 7, 2022

@ciampo yup! @ItsJonQ did a thorough performance evaluation here: ItsJonQ/g2#57

I think everything in that issue will still apply even though we're not using g2... most of the time g2 just passed straight through to Emotion anyway.

Do you think that's something worth looking into?

Probably worth spending some time try to reproduce it independent of Gutenberg... just so that we actually understand the why. We might be able to prevent similar problems in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Component System WordPress component system [Package] Components /packages/components [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants