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

Fallback to regular subscribe if the store doesn't exist in useSelect #27466

Merged

Conversation

kevin940726
Copy link
Member

Description

Fix an error when selecting a non-existing store in useSelect throw an error. A regression introduced in #26724. This pattern can be seen in some existing code before:

https://github.com/Automattic/wp-calypso/blob/9e0e3a8fdc49088aa005cf53c2ebf85a24f0ede4/apps/editing-toolkit/editing-toolkit-plugin/wpcom-block-editor-nux/src/wpcom-nux.js#L29

How has this been tested?

Added new test, run npm run test-unit.

Types of changes

Bug fix

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR.

@kevin940726 kevin940726 added [Type] Bug An existing feature does not function as intended [Package] Data /packages/data labels Dec 3, 2020
@kevin940726 kevin940726 requested a review from nerrad as a code owner December 3, 2020 11:47
@github-actions
Copy link

github-actions bot commented Dec 3, 2020

Size Change: +6 B (0%)

Total Size: 1.19 MB

Filename Size Change
build/data/index.js 8.98 kB +6 B (0%)
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 1.14 kB 0 B
build/annotations/index.js 3.8 kB 0 B
build/api-fetch/index.js 3.42 kB 0 B
build/autop/index.js 2.84 kB 0 B
build/blob/index.js 665 B 0 B
build/block-directory/index.js 8.72 kB 0 B
build/block-directory/style-rtl.css 943 B 0 B
build/block-directory/style.css 942 B 0 B
build/block-editor/index.js 128 kB 0 B
build/block-editor/style-rtl.css 11.2 kB 0 B
build/block-editor/style.css 11.2 kB 0 B
build/block-library/editor-rtl.css 9.07 kB 0 B
build/block-library/editor.css 9.07 kB 0 B
build/block-library/index.js 149 kB 0 B
build/block-library/style-rtl.css 8.34 kB 0 B
build/block-library/style.css 8.34 kB 0 B
build/block-library/theme-rtl.css 789 B 0 B
build/block-library/theme.css 790 B 0 B
build/block-serialization-default-parser/index.js 1.88 kB 0 B
build/block-serialization-spec-parser/index.js 3.06 kB 0 B
build/blocks/index.js 48.1 kB 0 B
build/components/index.js 172 kB 0 B
build/components/style-rtl.css 15.3 kB 0 B
build/components/style.css 15.3 kB 0 B
build/compose/index.js 9.95 kB 0 B
build/core-data/index.js 15.2 kB 0 B
build/data-controls/index.js 827 B 0 B
build/date/index.js 11.2 kB 0 B
build/deprecated/index.js 768 B 0 B
build/dom-ready/index.js 571 B 0 B
build/dom/index.js 4.95 kB 0 B
build/edit-navigation/index.js 11.1 kB 0 B
build/edit-navigation/style-rtl.css 881 B 0 B
build/edit-navigation/style.css 885 B 0 B
build/edit-post/index.js 306 kB 0 B
build/edit-post/style-rtl.css 6.49 kB 0 B
build/edit-post/style.css 6.47 kB 0 B
build/edit-site/index.js 24.5 kB 0 B
build/edit-site/style-rtl.css 3.91 kB 0 B
build/edit-site/style.css 3.91 kB 0 B
build/edit-widgets/index.js 26.3 kB 0 B
build/edit-widgets/style-rtl.css 3.13 kB 0 B
build/edit-widgets/style.css 3.13 kB 0 B
build/editor/editor-styles-rtl.css 476 B 0 B
build/editor/editor-styles.css 478 B 0 B
build/editor/index.js 43.6 kB 0 B
build/editor/style-rtl.css 3.85 kB 0 B
build/editor/style.css 3.84 kB 0 B
build/element/index.js 4.63 kB 0 B
build/escape-html/index.js 735 B 0 B
build/format-library/index.js 6.74 kB 0 B
build/format-library/style-rtl.css 547 B 0 B
build/format-library/style.css 548 B 0 B
build/hooks/index.js 2.27 kB 0 B
build/html-entities/index.js 623 B 0 B
build/i18n/index.js 3.57 kB 0 B
build/is-shallow-equal/index.js 698 B 0 B
build/keyboard-shortcuts/index.js 2.54 kB 0 B
build/keycodes/index.js 1.93 kB 0 B
build/list-reusable-blocks/index.js 3.1 kB 0 B
build/list-reusable-blocks/style-rtl.css 476 B 0 B
build/list-reusable-blocks/style.css 476 B 0 B
build/media-utils/index.js 5.31 kB 0 B
build/notices/index.js 1.82 kB 0 B
build/nux/index.js 3.42 kB 0 B
build/nux/style-rtl.css 671 B 0 B
build/nux/style.css 668 B 0 B
build/plugins/index.js 2.56 kB 0 B
build/primitives/index.js 1.43 kB 0 B
build/priority-queue/index.js 789 B 0 B
build/redux-routine/index.js 2.84 kB 0 B
build/reusable-blocks/index.js 2.92 kB 0 B
build/rich-text/index.js 13.4 kB 0 B
build/server-side-render/index.js 2.77 kB 0 B
build/shortcode/index.js 1.69 kB 0 B
build/token-list/index.js 1.27 kB 0 B
build/url/index.js 2.84 kB 0 B
build/viewport/index.js 1.86 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.22 kB 0 B

compressed-size-action

@youknowriad
Copy link
Contributor

youknowriad commented Dec 3, 2020

The question here becomes: what happens to a useSelect call if a store that were undefined is registered while the component is rendered. Ideally I think we should re call the mapSelectToProps function and resubscribe but my guess is that right now, it won't do anything.

@kevin940726
Copy link
Member Author

what happens to a useSelect call if a store that were undefined is registered while the component is rendered.

Yeah, that makes sense! I'll try to fix this tomorrow.

@fullofcaffeine
Copy link
Member

fullofcaffeine commented Dec 4, 2020

👋🏻 I'd like to share the findings of my investigation today:

We first thought that the problem was related to a specific store not being correctly registered, but it's a more general problem. It lies in the fact that select now saves the store name internally and that ends up in the listeningStore array Ref even if the store was never registered. In fact, the store might not be registered at all, and once gets to this loop where it calls __experimentalSubscribeStore for each of those store names collected from select calls, it will end up failing because it won't find that store in the registry.

I don't know why in some WP envs some stores might not be available before they're used. As Kai points out in the description, there's some prior art that confirms this behaviour might have been out there for a while. This guard prevents that component from breaking due to the unexisting store, but ends up causing this issue later because of the call to select adding the store name to the listenedStores.

This fix does seem to work well, and might be a suitable one for now. I wanted to further trigger the discussion, however, as there seems to be a sympton of a deeper issue here, for that, I have two questions:

  1. Why some stores are not available when needed?
  2. Should we also make sure we register all stores before we use them?

This might not pertain to Gutenberg itself, as the stores that caused the issue so far were registered by third-party lib. I'd like to know your thoughts about that, nevertheless.

Thanks! 🙇🏻

@noahtallen
Copy link
Member

Why some stores are not available when needed?

One thought that comes to my mind is that some JS code might execute before other code. For example, say I have two different WordPress plugins, both of which register block editor scripts. My one plugin contains a line which selects data from the other plugin. My other plugin registers that store (and also consumes it). I imagine the select code can be executed before the other plugin scripts have even gotten a chance to load, depending on which order the scripts load and are executed. In that situation, it would have caused a crash.

@youknowriad
Copy link
Contributor

I'd say it's either, the registration didn't happen yet (timing issues) or the registration happened on a separate @wordpress/data (bundling issues. But it seems it's not a Gutenberg issue.

const state = useSelect(
( select ) => ( {
count1: select( 'store-1' ).getCounter(),
blank: select( 'non-existing-store' )?.getCounter(),
Copy link
Member

Choose a reason for hiding this comment

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

Can we show a console.warn when selecting a non-existing store? Similar to as we do when registering blocks with invalid category. This way we'd also immediately see that a store is not available (whatever the reason), which could help to track issues that it might cause.

Copy link
Member Author

Choose a reason for hiding this comment

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

This has been brought up internally, c.c. @youknowriad.

The counter point is that sometimes it's intended, as shown in the example in the PR description. I'm not sure if it's an anti-pattern or not, but this pattern does exist. I think it needs further discussion and more usage examples.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, gotcha. Maybe we could consider adding sth like isStoreRegistered to @wordpress/data then. This does sound like a topic for another issue though 👍

@kevin940726
Copy link
Member Author

For now, I try to fallback to regular registry.subscribe for these cases, as the implementation would be too complicated. We should discuss whether we consider this as an anti-pattern or not, especially when we now recommend selecting store instance directly rather than selecting from string literal.

@kevin940726 kevin940726 changed the title Gracefully swallow the error if the store doesn't exist in useSelect Fallback to regular subscribe if the store doesn't exist in useSelect Dec 4, 2020
@WunderBart
Copy link
Member

Why some stores are not available when needed?

One thought that comes to my mind is that some JS code might execute before other code. For example, say I have two different WordPress plugins, both of which register block editor scripts. My one plugin contains a line which selects data from the other plugin. My other plugin registers that store (and also consumes it). I imagine the select code can be executed before the other plugin scripts have even gotten a chance to load, depending on which order the scripts load and are executed. In that situation, it would have caused a crash.

I did some debugging there and it seems to be exactly what happened in our case, @noahtallen - a select( 'foo' ) called before foo was registered. It happened because it was called from an independent module, without dependency control towards the other module that registers foo. cc @fullofcaffeine

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

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

Are we losing performance or we're just in the error range? Thanks for the tests.

@kevin940726 kevin940726 force-pushed the update/swallow-error-when-subscribe-to-non-exisiting-store branch from cd2f37e to da91d42 Compare December 7, 2020 02:15
@kevin940726
Copy link
Member Author

I just rebased master, I don't think we're losing performance now, thx for the check!

@kevin940726 kevin940726 merged commit 6cfe7a7 into master Dec 7, 2020
@kevin940726 kevin940726 deleted the update/swallow-error-when-subscribe-to-non-exisiting-store branch December 7, 2020 03:01
@github-actions github-actions bot added this to the Gutenberg 9.6 milestone Dec 7, 2020
@WunderBart WunderBart mentioned this pull request Dec 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Data /packages/data [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants