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

[RNMobile] Prevent calling the registerComponent callback multiple times #32985

Merged
merged 3 commits into from
Jul 15, 2021

Conversation

fluiddot
Copy link
Contributor

@fluiddot fluiddot commented Jun 25, 2021

Description

On development mode, I noticed that the callback executed when registering a component in the App registry is called multiple times. This case happens when the code within the callback triggers any warning or error (this means calling console.warning or console.error), in this issue there's additional helpful information regarding this.

This issue has been addressed by adding a static object that contains the components that have been already registered, this way we enforce that the callback's code will be executed only once.

EDIT: I finally followed a different approach to address this problem, based on the AppRegistry documentation, I created a class component that holds the initialization logic.

How has this been tested?

  1. Add the following code debugger; before this line
  2. Run the app connected with Metro and debug it with Chrome
  3. Open a post/page
  4. Observe that the debugger gets paused due to the code above.
  5. Resume the script execution Screenshot 2021-06-25 at 13 35 20
  6. Observe that the debugger is not paused again.

Verify that reusable blocks are rendered

The original issue was spotted due to the reusable blocks being rendered as unsupported blocks so it would be nice to verify that the issue is no longer happening.

  1. Open a post/page that contains reusable blocks (if required add them via the web version of the editor)
  2. Observe that the reusable blocks are rendered properly

Screenshots

N/A

Types of changes

Bug 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.
  • 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).

@fluiddot fluiddot added the Mobile App - i.e. Android or iOS Native mobile impl of the block editor. (Note: used in scripts, ping mobile folks to change) label Jun 25, 2021
@fluiddot fluiddot self-assigned this Jun 25, 2021
@fluiddot fluiddot linked an issue Jun 25, 2021 that may be closed by this pull request
@fluiddot fluiddot requested a review from hypest June 25, 2021 11:46
@github-actions
Copy link

github-actions bot commented Jun 25, 2021

Size Change: 0 B

Total Size: 1.07 MB

ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 1.12 kB
build/admin-manifest/index.min.js 1.41 kB
build/annotations/index.min.js 2.93 kB
build/api-fetch/index.min.js 2.44 kB
build/autop/index.min.js 2.28 kB
build/blob/index.min.js 673 B
build/block-directory/index.min.js 6.61 kB
build/block-directory/style-rtl.css 1.02 kB
build/block-directory/style.css 1.02 kB
build/block-editor/index.min.js 126 kB
build/block-editor/style-rtl.css 14 kB
build/block-editor/style.css 14 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 58 B
build/block-library/blocks/audio/editor.css 58 B
build/block-library/blocks/audio/style-rtl.css 112 B
build/block-library/blocks/audio/style.css 112 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 475 B
build/block-library/blocks/button/editor.css 474 B
build/block-library/blocks/button/style-rtl.css 603 B
build/block-library/blocks/button/style.css 602 B
build/block-library/blocks/buttons/editor-rtl.css 315 B
build/block-library/blocks/buttons/editor.css 315 B
build/block-library/blocks/buttons/style-rtl.css 375 B
build/block-library/blocks/buttons/style.css 375 B
build/block-library/blocks/calendar/style-rtl.css 208 B
build/block-library/blocks/calendar/style.css 208 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 190 B
build/block-library/blocks/columns/editor.css 190 B
build/block-library/blocks/columns/style-rtl.css 475 B
build/block-library/blocks/columns/style.css 476 B
build/block-library/blocks/cover/editor-rtl.css 670 B
build/block-library/blocks/cover/editor.css 670 B
build/block-library/blocks/cover/style-rtl.css 1.22 kB
build/block-library/blocks/cover/style.css 1.23 kB
build/block-library/blocks/embed/editor-rtl.css 486 B
build/block-library/blocks/embed/editor.css 486 B
build/block-library/blocks/embed/style-rtl.css 401 B
build/block-library/blocks/embed/style.css 400 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 301 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 780 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 704 B
build/block-library/blocks/gallery/editor.css 705 B
build/block-library/blocks/gallery/style-rtl.css 1.06 kB
build/block-library/blocks/gallery/style.css 1.06 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 160 B
build/block-library/blocks/group/editor.css 160 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 93 B
build/block-library/blocks/group/theme.css 93 B
build/block-library/blocks/heading/editor-rtl.css 152 B
build/block-library/blocks/heading/editor.css 152 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/home-link/style-rtl.css 259 B
build/block-library/blocks/home-link/style.css 259 B
build/block-library/blocks/html/editor-rtl.css 281 B
build/block-library/blocks/html/editor.css 281 B
build/block-library/blocks/image/editor-rtl.css 729 B
build/block-library/blocks/image/editor.css 727 B
build/block-library/blocks/image/style-rtl.css 481 B
build/block-library/blocks/image/style.css 485 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 286 B
build/block-library/blocks/latest-comments/style.css 286 B
build/block-library/blocks/latest-posts/editor-rtl.css 137 B
build/block-library/blocks/latest-posts/editor.css 137 B
build/block-library/blocks/latest-posts/style-rtl.css 526 B
build/block-library/blocks/latest-posts/style.css 524 B
build/block-library/blocks/list/style-rtl.css 63 B
build/block-library/blocks/list/style.css 63 B
build/block-library/blocks/media-text/editor-rtl.css 263 B
build/block-library/blocks/media-text/editor.css 264 B
build/block-library/blocks/media-text/style-rtl.css 492 B
build/block-library/blocks/media-text/style.css 489 B
build/block-library/blocks/more/editor-rtl.css 434 B
build/block-library/blocks/more/editor.css 434 B
build/block-library/blocks/navigation-link/editor-rtl.css 474 B
build/block-library/blocks/navigation-link/editor.css 473 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/editor-rtl.css 1.69 kB
build/block-library/blocks/navigation/editor.css 1.69 kB
build/block-library/blocks/navigation/style-rtl.css 1.65 kB
build/block-library/blocks/navigation/style.css 1.66 kB
build/block-library/blocks/navigation/view.min.js 2.87 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 310 B
build/block-library/blocks/page-list/editor.css 311 B
build/block-library/blocks/page-list/style-rtl.css 240 B
build/block-library/blocks/page-list/style.css 240 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 247 B
build/block-library/blocks/paragraph/style.css 248 B
build/block-library/blocks/post-author/editor-rtl.css 209 B
build/block-library/blocks/post-author/editor.css 209 B
build/block-library/blocks/post-author/style-rtl.css 183 B
build/block-library/blocks/post-author/style.css 184 B
build/block-library/blocks/post-comments-form/style-rtl.css 140 B
build/block-library/blocks/post-comments-form/style.css 140 B
build/block-library/blocks/post-comments/style-rtl.css 360 B
build/block-library/blocks/post-comments/style.css 359 B
build/block-library/blocks/post-content/editor-rtl.css 139 B
build/block-library/blocks/post-content/editor.css 139 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 338 B
build/block-library/blocks/post-featured-image/editor.css 338 B
build/block-library/blocks/post-featured-image/style-rtl.css 141 B
build/block-library/blocks/post-featured-image/style.css 141 B
build/block-library/blocks/post-template/editor-rtl.css 100 B
build/block-library/blocks/post-template/editor.css 99 B
build/block-library/blocks/post-template/style-rtl.css 379 B
build/block-library/blocks/post-template/style.css 380 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 60 B
build/block-library/blocks/post-title/style.css 60 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 183 B
build/block-library/blocks/pullquote/editor.css 183 B
build/block-library/blocks/pullquote/style-rtl.css 318 B
build/block-library/blocks/pullquote/style.css 318 B
build/block-library/blocks/pullquote/theme-rtl.css 169 B
build/block-library/blocks/pullquote/theme.css 169 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 270 B
build/block-library/blocks/query-pagination/editor.css 262 B
build/block-library/blocks/query-pagination/style-rtl.css 168 B
build/block-library/blocks/query-pagination/style.css 168 B
build/block-library/blocks/query-title/editor-rtl.css 86 B
build/block-library/blocks/query-title/editor.css 86 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 169 B
build/block-library/blocks/quote/style.css 169 B
build/block-library/blocks/quote/theme-rtl.css 221 B
build/block-library/blocks/quote/theme.css 221 B
build/block-library/blocks/rss/editor-rtl.css 201 B
build/block-library/blocks/rss/editor.css 202 B
build/block-library/blocks/rss/style-rtl.css 290 B
build/block-library/blocks/rss/style.css 290 B
build/block-library/blocks/search/editor-rtl.css 211 B
build/block-library/blocks/search/editor.css 211 B
build/block-library/blocks/search/style-rtl.css 359 B
build/block-library/blocks/search/style.css 362 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 251 B
build/block-library/blocks/separator/style.css 251 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 476 B
build/block-library/blocks/shortcode/editor.css 476 B
build/block-library/blocks/site-logo/editor-rtl.css 465 B
build/block-library/blocks/site-logo/editor.css 465 B
build/block-library/blocks/site-logo/style-rtl.css 154 B
build/block-library/blocks/site-logo/style.css 154 B
build/block-library/blocks/site-tagline/editor-rtl.css 87 B
build/block-library/blocks/site-tagline/editor.css 87 B
build/block-library/blocks/site-title/editor-rtl.css 85 B
build/block-library/blocks/site-title/editor.css 85 B
build/block-library/blocks/social-link/editor-rtl.css 164 B
build/block-library/blocks/social-link/editor.css 165 B
build/block-library/blocks/social-links/editor-rtl.css 800 B
build/block-library/blocks/social-links/editor.css 799 B
build/block-library/blocks/social-links/style-rtl.css 1.34 kB
build/block-library/blocks/social-links/style.css 1.34 kB
build/block-library/blocks/spacer/editor-rtl.css 308 B
build/block-library/blocks/spacer/editor.css 308 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 478 B
build/block-library/blocks/table/editor.css 478 B
build/block-library/blocks/table/style-rtl.css 480 B
build/block-library/blocks/table/style.css 480 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 146 B
build/block-library/blocks/tag-cloud/style.css 146 B
build/block-library/blocks/template-part/editor-rtl.css 551 B
build/block-library/blocks/template-part/editor.css 550 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/term-description/editor-rtl.css 90 B
build/block-library/blocks/term-description/editor.css 90 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 569 B
build/block-library/blocks/video/editor.css 570 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 1.29 kB
build/block-library/common.css 1.29 kB
build/block-library/editor-rtl.css 9.81 kB
build/block-library/editor.css 9.81 kB
build/block-library/index.min.js 147 kB
build/block-library/reset-rtl.css 514 B
build/block-library/reset.css 515 B
build/block-library/style-rtl.css 10.3 kB
build/block-library/style.css 10.3 kB
build/block-library/theme-rtl.css 692 B
build/block-library/theme.css 693 B
build/block-serialization-default-parser/index.min.js 1.3 kB
build/block-serialization-spec-parser/index.min.js 3.06 kB
build/blocks/index.min.js 47.2 kB
build/components/index.min.js 196 kB
build/components/style-rtl.css 16.1 kB
build/components/style.css 16.1 kB
build/compose/index.min.js 10.2 kB
build/core-data/index.min.js 12.4 kB
build/customize-widgets/index.min.js 10.3 kB
build/customize-widgets/style-rtl.css 1.48 kB
build/customize-widgets/style.css 1.48 kB
build/data-controls/index.min.js 830 B
build/data/index.min.js 7.22 kB
build/date/index.min.js 31.8 kB
build/deprecated/index.min.js 738 B
build/dom-ready/index.min.js 576 B
build/dom/index.min.js 4.78 kB
build/edit-navigation/index.min.js 13.9 kB
build/edit-navigation/style-rtl.css 3.12 kB
build/edit-navigation/style.css 3.12 kB
build/edit-post/classic-rtl.css 483 B
build/edit-post/classic.css 483 B
build/edit-post/index.min.js 59.5 kB
build/edit-post/style-rtl.css 7.25 kB
build/edit-post/style.css 7.24 kB
build/edit-site/index.min.js 26 kB
build/edit-site/style-rtl.css 5.04 kB
build/edit-site/style.css 5.03 kB
build/edit-widgets/index.min.js 16.2 kB
build/edit-widgets/style-rtl.css 3.88 kB
build/edit-widgets/style.css 3.89 kB
build/editor/index.min.js 38.7 kB
build/editor/style-rtl.css 3.88 kB
build/editor/style.css 3.88 kB
build/element/index.min.js 3.44 kB
build/escape-html/index.min.js 739 B
build/format-library/index.min.js 5.71 kB
build/format-library/style-rtl.css 668 B
build/format-library/style.css 669 B
build/hooks/index.min.js 1.76 kB
build/html-entities/index.min.js 628 B
build/i18n/index.min.js 3.73 kB
build/is-shallow-equal/index.min.js 709 B
build/keyboard-shortcuts/index.min.js 1.74 kB
build/keycodes/index.min.js 1.43 kB
build/list-reusable-blocks/index.min.js 2.07 kB
build/list-reusable-blocks/style-rtl.css 842 B
build/list-reusable-blocks/style.css 842 B
build/media-utils/index.min.js 3.08 kB
build/notices/index.min.js 1.07 kB
build/nux/index.min.js 2.31 kB
build/nux/style-rtl.css 745 B
build/nux/style.css 742 B
build/plugins/index.min.js 1.99 kB
build/primitives/index.min.js 1.06 kB
build/priority-queue/index.min.js 790 B
build/react-i18n/index.min.js 924 B
build/redux-routine/index.min.js 2.82 kB
build/reusable-blocks/index.min.js 2.56 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 10.8 kB
build/server-side-render/index.min.js 1.64 kB
build/shortcode/index.min.js 1.68 kB
build/token-list/index.min.js 847 B
build/url/index.min.js 1.95 kB
build/viewport/index.min.js 1.28 kB
build/warning/index.min.js 1.16 kB
build/widgets/index.min.js 6.48 kB
build/widgets/style-rtl.css 1.04 kB
build/widgets/style.css 1.05 kB
build/wordcount/index.min.js 1.24 kB

compressed-size-action

@hypest
Copy link
Contributor

hypest commented Jun 28, 2021

This issue has been addressed by adding a static object that contains the components that have been already registered, this way we enforce that the callback's code will be executed only once.

👋 @fluiddot ! Gave a first look at the solution here and even though I understand how it works, I don't feel particularly confident about fighting against the current lifecycle of the app and components by keeping a static variable. I feel that this is a indication that the lifecycle is not clean enough (on one hand) and that adding a static variable to combat it will only move the problem elsewhere. I understand that this feeling is not super specific or concrete though.

Since the issue here only happens in the development build, I wonder if we should just spend some more time finding a different solution, one that would probably leave the component lifecycle in a better state? Let me know what you think.

@fluiddot
Copy link
Contributor Author

This issue has been addressed by adding a static object that contains the components that have been already registered, this way we enforce that the callback's code will be executed only once.

👋 @fluiddot ! Gave a first look at the solution here and even though I understand how it works, I don't feel particularly confident about fighting against the current lifecycle of the app and components by keeping a static variable. I feel that this is a indication that the lifecycle is not clean enough (on one hand) and that adding a static variable to combat it will only move the problem elsewhere. UI understand that this feeling is not super specific or concrete though.

Since the issue here only happens in the development build, I wonder if we should just spend some more time finding a different solution, one that would probably leave the component lifecycle in a better state? Let me know what you think.

My initial approach when I tackled this issue was to figure out why we have some differences between the production and development modes, like the reusable blocks being unsupported. After the investigation, I realized that was related to the App component's lifecycle and logging errors/warnings, so I wanted to propose this PR as a workaround but I agree that is more like a hack than an actual solution. I have some ideas that I'd like to explore so I'll set the PR to draft and propose a new solution.

Thanks for reviewing it 🙇 !

@fluiddot fluiddot marked this pull request as draft June 28, 2021 13:47
return cloneElement( element, filteredProps );
} );
componentDidMount() {
doAction( 'native.render', this.filteredProps );
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand that the action native.render should happen after the component is rendered, so now this will be executed after the component is mounted, this means after the first render.

'native.block_editor_props',
parentProps
);
doAction( 'native.pre-render', parentProps );
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The native.pre-render action will be executed right before the component is mounted, this means before the first render.

Copy link
Member

Choose a reason for hiding this comment

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

I do not follow exactly how the "ceremony" of a class addresses the original issue of multiple calls to native.pre-render. Is render/AppRegistry.registerComponent still called multiple times? Is the class instance retained between the calls?

Copy link
Contributor Author

@fluiddot fluiddot Jul 13, 2021

Choose a reason for hiding this comment

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

As far as I checked, the multiple calls are caused when returning a function instead of a component when registering the component in AppRegistry. For example, if you replace line 41 with the following code:

	AppRegistry.registerComponent( id, () => ( props ) => {
		debugger;
		return <App { ...props } />;
	} );

The debugger will pause for every warning and error that happens during the initialization, I haven't located yet the exact part from the React Native code that causes this. My impression is that when a warning/error is handled and tries to get the component stack, for some reason is calling the function returned from the callback passed in the AppRegistry.registerComponent call.

I haven't checked with a functional component, but in this case, since we need to execute the pre-render action on the constructor, I haven't found a reliable way to do it with a functional component so I used a classic component.

Copy link
Member

Choose a reason for hiding this comment

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

The debugger will pause for every warning and error that happens during the initialization, I haven't located yet the exact part from the React Native code that causes this. My impression is that when a warning/error is handled and tries to get the component stack, for some reason is calling the function returned from the callback passed in the AppRegistry.registerComponent call.

Ah, I see. From your statement and debugging myself a little further my perception is that the component registered with AppRegistry is merely re-rendering when these warnings/errors occur. I presume that is expected, but I have no explicit source to back up that presumption. However, I now understand the doAction calls to be side effects and shouldn't be invoked on every render.

I haven't checked with a functional component, but in this case, since we need to execute the pre-render action on the constructor, I haven't found a reliable way to do it with a functional component so I used a classic component.

From a brief test, wrapping the side effects with useEffect does appear to have the same outcome, i.e. preventing multiple runs, but there is not explicit reason to avoid a class. So, leveraging the class approach is OK with me.

Functional App
/**
 * External dependencies
 */
import { AppRegistry } from 'react-native';
import { omit } from 'lodash';

/**
 * WordPress dependencies
 */
import { applyFilters, doAction } from '@wordpress/hooks';
import { useEffect } from '@wordpress/element';

/**
 * Internal dependencies
 */
import { cloneElement } from './react';

const render = ( element, id ) =>
	AppRegistry.registerComponent( id, () => ( propsFromParent ) => {
		const parentProps = omit( propsFromParent || {}, [ 'rootTag' ] );

		useEffect( () => {
			doAction( 'native.pre-render', parentProps );
		}, [] );

		const filteredProps = applyFilters(
			'native.block_editor_props',
			parentProps
		);

		useEffect( () => {
			doAction( 'native.render', filteredProps );
		}, [] );

		return cloneElement( element, filteredProps );
	} );

/**
 * Render a given element on Native.
 * This actually returns a componentProvider that can be registered with `AppRegistry.registerComponent`
 *
 * @param {WPElement} element Element to render.
 */
export { render };

In regards to "we need to execute the pre-render action on the constructor," is this to imply that doAction( 'native.render', filteredProps ); (or other code) is dependent upon doAction( 'native.pre-render', parentProps );? Or was your statement merely your interpretation based upon the existing name "pre-render"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The debugger will pause for every warning and error that happens during the initialization, I haven't located yet the exact part from the React Native code that causes this. My impression is that when a warning/error is handled and tries to get the component stack, for some reason is calling the function returned from the callback passed in the AppRegistry.registerComponent call.

Ah, I see. From your statement and debugging myself a little further my perception is that the component registered with AppRegistry is merely re-rendering when these warnings/errors occur. I presume that is expected, but I have no explicit source to back up that presumption. However, I now understand the doAction calls to be side effects and shouldn't be invoked on every render.

Exactly, the doAction calls should be treated as side effects and only executed once.

Regarding why the function is being called, it's a bit hard to follow the trace but it's related to how the react-devtools package builds the component stacktrace when a warning/error is triggered, here are some screenshots to clarify it:

1. Log warning is triggered
log-warning

2. Getting the component stack (react-dev-tools)

The important part here is the current variable that holds the current Fiber node and is passed down the stack trace.
fibernode

Here you can see that the type property of the current Fiber node is the App function:
fibernode-type

3. Describe function component frame

The previous current value is passed as the workInProgress argument in the function shown in the screenshot, note that the first argument of the call to describeFunctionComponentFrame is the type value (the App function) that was mentioned previosly.
describe-fiber

4. Describe native component frame - App function call
fn-call

I haven't checked with a functional component, but in this case, since we need to execute the pre-render action on the constructor, I haven't found a reliable way to do it with a functional component so I used a classic component.

From a brief test, wrapping the side effects with useEffect does appear to have the same outcome, i.e. preventing multiple runs, but there is not explicit reason to avoid a class. So, leveraging the class approach is OK with me.

Thanks for sharing the functional component approach 🙇 ! I agree that wrapping the doAction calls with useEffect is enough to prevent calling them multiple times, but I decided to go with the class approach because I thought that the native.pre-render call should happen before the component is rendered and useEffect is triggered after the first render. Due to this reason is why I added the native.pre-render action in the class contructor, I found some workarounds to run code on a functional component before the first render but looked like hacks.

In regards to "we need to execute the pre-render action on the constructor," is this to imply that doAction( 'native.render', filteredProps ); (or other code) is dependent upon doAction( 'native.pre-render', parentProps );? Or was your statement merely your interpretation based upon the existing name "pre-render"?

As far as I checked in the file where the actions are defined, I don't feel there are any dependency between the actions but I infer from the action's name native.pre-render that this should be called before the editor is rendered 🤔 .

Copy link
Member

Choose a reason for hiding this comment

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

Regarding why the function is being called, it's a bit hard to follow the trace but it's related to how the react-devtools package builds the component stacktrace when a warning/error is triggered [...]

Yeah, I did notice — if I remembering correctly — the original multiple side effect calls issue only occurs if Debug is enabled from the developer menu.

As far as I checked in the file where the actions are defined, I don't feel there are any dependency between the actions but I infer from the action's name native.pre-render that this should be called before the editor is rendered 🤔 .

Makes sense. I have no data to suggest otherwise at this point. The class constructor approach does ensure it invokes before initial render.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I did notice — if I remembering correctly — the original multiple side effect calls issue only occurs if Debug is enabled from the developer menu.

I think it also happens when the Debug is disabled because the reusable blocks are still not showing up in that case. I infer it's being called multiple times because the reusable block is being unregistered when this code gets executed a second time, surprisingly the capabilities object is empty and since the reusable block has been already registered, the condition is true.

const capabilities = props.capabilities ?? {};
if (
getBlockType( 'core/block' ) !== undefined &&
capabilities.reusableBlock !== true
) {
unregisterBlockType( 'core/block' );
}

Makes sense. I have no data to suggest otherwise at this point. The class constructor approach does ensure it invokes before initial render.

👍

AppRegistry.registerComponent( id, () => ( propsFromParent ) => {
const parentProps = omit( propsFromParent || {}, [ 'rootTag' ] );
const render = ( element, id ) => {
class App extends Component {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The App class component is defined within the render function because it uses the element argument to render the component.

@fluiddot fluiddot force-pushed the rnmobile/fix/prevent-pre-render-hook-multiple-times branch from dc27a49 to f392e04 Compare July 9, 2021 14:43
@fluiddot fluiddot marked this pull request as ready for review July 9, 2021 14:44
@fluiddot
Copy link
Contributor Author

fluiddot commented Jul 9, 2021

Since this part is quite critical I wanted to have more 👀 to review this, @geriux @dcalhoun I'd appreciate it if you could take a look at this refactor of the initialization logic of the editor in RN, thanks 🙇 !

Copy link
Member

@dcalhoun dcalhoun left a comment

Choose a reason for hiding this comment

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

👍🏻 Verified the the debugger was only hit once and Reusable Blocks functioned on both an iPhone SE and Samsung Galaxy S20.

'native.block_editor_props',
parentProps
);
doAction( 'native.pre-render', parentProps );
Copy link
Member

Choose a reason for hiding this comment

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

I do not follow exactly how the "ceremony" of a class addresses the original issue of multiple calls to native.pre-render. Is render/AppRegistry.registerComponent still called multiple times? Is the class instance retained between the calls?

Copy link
Member

@dcalhoun dcalhoun left a comment

Choose a reason for hiding this comment

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

LGTM. 👍🏻 My earlier #32985 (review) verified the app functioned as expected. Thank you for sorting this out. 🙇🏻

'native.block_editor_props',
parentProps
);
doAction( 'native.pre-render', parentProps );
Copy link
Member

Choose a reason for hiding this comment

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

Regarding why the function is being called, it's a bit hard to follow the trace but it's related to how the react-devtools package builds the component stacktrace when a warning/error is triggered [...]

Yeah, I did notice — if I remembering correctly — the original multiple side effect calls issue only occurs if Debug is enabled from the developer menu.

As far as I checked in the file where the actions are defined, I don't feel there are any dependency between the actions but I infer from the action's name native.pre-render that this should be called before the editor is rendered 🤔 .

Makes sense. I have no data to suggest otherwise at this point. The class constructor approach does ensure it invokes before initial render.

@fluiddot fluiddot merged commit a7ba291 into trunk Jul 15, 2021
@fluiddot fluiddot deleted the rnmobile/fix/prevent-pre-render-hook-multiple-times branch July 15, 2021 10:21
@github-actions github-actions bot added this to the Gutenberg 11.2 milestone Jul 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Mobile App - i.e. Android or iOS Native mobile impl of the block editor. (Note: used in scripts, ping mobile folks to change)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reusable blocks are displayed as unsupported (development mode only)
3 participants