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

Components: Try to make the order of fills stable in regular slots #29287

Merged
merged 3 commits into from
Mar 4, 2021

Conversation

gziolo
Copy link
Member

@gziolo gziolo commented Feb 24, 2021

Description

This PR tries to resolve #28546.

If you unpin a plugin sidebar, then pin it again, it's now the first pinned item, which is problematic as the Settings cog should always be the first icon:

106005578-a4c9cb00-60b4-11eb-98c6-bd731ce38582

The fact that upon a page reload, the pinned item is back where it "should" be, suggests it's a bug.

How has this been tested?

Register a few sidebar plugins that are pinnable, for example, plugins that offer integration: Yoast, Jetpack. Make sure that the order of pinned items remains the same when you pin and unpin them several times.

Screenshots

Screen.Recording.2021-02-24.at.08.21.10.mov

bug

Types of changes

Enhancement.

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.

@gziolo gziolo marked this pull request as draft February 24, 2021 07:04
@@ -54,6 +55,12 @@ class PluginArea extends Component {
super( ...arguments );

this.setPlugins = this.setPlugins.bind( this );
this.memoizedContext = memoize( ( name, icon ) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

While it decreases the number of rerenders it doesn't quite solve the issue when using the concept of occurrences.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is this related to ordering, or did we notice a chance to reduce rerenders?

Copy link
Member Author

@gziolo gziolo Mar 2, 2021

Choose a reason for hiding this comment

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

It doesn't have any impact on the ordering but prevents excessive rerenders. I suspected initially it might cause some random unmounting, but my tests didn't confirm that.

@github-actions
Copy link

github-actions bot commented Feb 24, 2021

Size Change: +194 B (0%)

Total Size: 1.39 MB

Filename Size Change
build/components/index.js 272 kB -83 B (0%)
build/plugins/index.js 2.88 kB +277 B (+11%) ⚠️
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 1.14 kB 0 B
build/annotations/index.js 3.79 kB 0 B
build/api-fetch/index.js 3.41 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 9.1 kB 0 B
build/block-directory/style-rtl.css 1.01 kB 0 B
build/block-directory/style.css 1.01 kB 0 B
build/block-editor/index.js 125 kB 0 B
build/block-editor/style-rtl.css 12.1 kB 0 B
build/block-editor/style.css 12.1 kB 0 B
build/block-library/blocks/archives/editor-rtl.css 61 B 0 B
build/block-library/blocks/archives/editor.css 60 B 0 B
build/block-library/blocks/audio/editor-rtl.css 58 B 0 B
build/block-library/blocks/audio/editor.css 58 B 0 B
build/block-library/blocks/audio/style-rtl.css 103 B 0 B
build/block-library/blocks/audio/style.css 103 B 0 B
build/block-library/blocks/block/editor-rtl.css 161 B 0 B
build/block-library/blocks/block/editor.css 161 B 0 B
build/block-library/blocks/button/editor-rtl.css 475 B 0 B
build/block-library/blocks/button/editor.css 474 B 0 B
build/block-library/blocks/button/style-rtl.css 479 B 0 B
build/block-library/blocks/button/style.css 479 B 0 B
build/block-library/blocks/buttons/editor-rtl.css 315 B 0 B
build/block-library/blocks/buttons/editor.css 315 B 0 B
build/block-library/blocks/buttons/style-rtl.css 364 B 0 B
build/block-library/blocks/buttons/style.css 363 B 0 B
build/block-library/blocks/calendar/style-rtl.css 208 B 0 B
build/block-library/blocks/calendar/style.css 208 B 0 B
build/block-library/blocks/categories/editor-rtl.css 84 B 0 B
build/block-library/blocks/categories/editor.css 83 B 0 B
build/block-library/blocks/categories/style-rtl.css 79 B 0 B
build/block-library/blocks/categories/style.css 79 B 0 B
build/block-library/blocks/code/style-rtl.css 90 B 0 B
build/block-library/blocks/code/style.css 90 B 0 B
build/block-library/blocks/columns/editor-rtl.css 190 B 0 B
build/block-library/blocks/columns/editor.css 190 B 0 B
build/block-library/blocks/columns/style-rtl.css 421 B 0 B
build/block-library/blocks/columns/style.css 421 B 0 B
build/block-library/blocks/cover/editor-rtl.css 390 B 0 B
build/block-library/blocks/cover/editor.css 389 B 0 B
build/block-library/blocks/cover/style-rtl.css 1.25 kB 0 B
build/block-library/blocks/cover/style.css 1.25 kB 0 B
build/block-library/blocks/embed/editor-rtl.css 486 B 0 B
build/block-library/blocks/embed/editor.css 486 B 0 B
build/block-library/blocks/embed/style-rtl.css 396 B 0 B
build/block-library/blocks/embed/style.css 395 B 0 B
build/block-library/blocks/file/editor-rtl.css 199 B 0 B
build/block-library/blocks/file/editor.css 198 B 0 B
build/block-library/blocks/file/style-rtl.css 248 B 0 B
build/block-library/blocks/file/style.css 248 B 0 B
build/block-library/blocks/freeform/editor-rtl.css 2.45 kB 0 B
build/block-library/blocks/freeform/editor.css 2.45 kB 0 B
build/block-library/blocks/gallery/editor-rtl.css 689 B 0 B
build/block-library/blocks/gallery/editor.css 690 B 0 B
build/block-library/blocks/gallery/style-rtl.css 1.07 kB 0 B
build/block-library/blocks/gallery/style.css 1.06 kB 0 B
build/block-library/blocks/group/editor-rtl.css 318 B 0 B
build/block-library/blocks/group/editor.css 317 B 0 B
build/block-library/blocks/group/style-rtl.css 57 B 0 B
build/block-library/blocks/group/style.css 57 B 0 B
build/block-library/blocks/heading/editor-rtl.css 129 B 0 B
build/block-library/blocks/heading/editor.css 129 B 0 B
build/block-library/blocks/heading/style-rtl.css 76 B 0 B
build/block-library/blocks/heading/style.css 76 B 0 B
build/block-library/blocks/html/editor-rtl.css 281 B 0 B
build/block-library/blocks/html/editor.css 281 B 0 B
build/block-library/blocks/image/editor-rtl.css 717 B 0 B
build/block-library/blocks/image/editor.css 716 B 0 B
build/block-library/blocks/image/style-rtl.css 477 B 0 B
build/block-library/blocks/image/style.css 478 B 0 B
build/block-library/blocks/latest-comments/editor-rtl.css 159 B 0 B
build/block-library/blocks/latest-comments/editor.css 158 B 0 B
build/block-library/blocks/latest-comments/style-rtl.css 269 B 0 B
build/block-library/blocks/latest-comments/style.css 269 B 0 B
build/block-library/blocks/latest-posts/editor-rtl.css 137 B 0 B
build/block-library/blocks/latest-posts/editor.css 137 B 0 B
build/block-library/blocks/latest-posts/style-rtl.css 523 B 0 B
build/block-library/blocks/latest-posts/style.css 522 B 0 B
build/block-library/blocks/list/editor-rtl.css 65 B 0 B
build/block-library/blocks/list/editor.css 65 B 0 B
build/block-library/blocks/list/style-rtl.css 63 B 0 B
build/block-library/blocks/list/style.css 63 B 0 B
build/block-library/blocks/media-text/editor-rtl.css 191 B 0 B
build/block-library/blocks/media-text/editor.css 191 B 0 B
build/block-library/blocks/media-text/style-rtl.css 535 B 0 B
build/block-library/blocks/media-text/style.css 532 B 0 B
build/block-library/blocks/more/editor-rtl.css 434 B 0 B
build/block-library/blocks/more/editor.css 434 B 0 B
build/block-library/blocks/navigation-link/editor-rtl.css 681 B 0 B
build/block-library/blocks/navigation-link/editor.css 683 B 0 B
build/block-library/blocks/navigation-link/style-rtl.css 694 B 0 B
build/block-library/blocks/navigation-link/style.css 692 B 0 B
build/block-library/blocks/navigation/editor-rtl.css 1.34 kB 0 B
build/block-library/blocks/navigation/editor.css 1.34 kB 0 B
build/block-library/blocks/navigation/style-rtl.css 213 B 0 B
build/block-library/blocks/navigation/style.css 214 B 0 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B 0 B
build/block-library/blocks/nextpage/editor.css 395 B 0 B
build/block-library/blocks/page-list/editor-rtl.css 214 B 0 B
build/block-library/blocks/page-list/editor.css 214 B 0 B
build/block-library/blocks/page-list/style-rtl.css 527 B 0 B
build/block-library/blocks/page-list/style.css 526 B 0 B
build/block-library/blocks/paragraph/editor-rtl.css 109 B 0 B
build/block-library/blocks/paragraph/editor.css 109 B 0 B
build/block-library/blocks/paragraph/style-rtl.css 288 B 0 B
build/block-library/blocks/paragraph/style.css 289 B 0 B
build/block-library/blocks/post-author/editor-rtl.css 209 B 0 B
build/block-library/blocks/post-author/editor.css 209 B 0 B
build/block-library/blocks/post-author/style-rtl.css 183 B 0 B
build/block-library/blocks/post-author/style.css 184 B 0 B
build/block-library/blocks/post-comments-form/style-rtl.css 250 B 0 B
build/block-library/blocks/post-comments-form/style.css 250 B 0 B
build/block-library/blocks/post-content/editor-rtl.css 139 B 0 B
build/block-library/blocks/post-content/editor.css 139 B 0 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B 0 B
build/block-library/blocks/post-excerpt/editor.css 73 B 0 B
build/block-library/blocks/post-featured-image/editor-rtl.css 338 B 0 B
build/block-library/blocks/post-featured-image/editor.css 338 B 0 B
build/block-library/blocks/post-featured-image/style-rtl.css 100 B 0 B
build/block-library/blocks/post-featured-image/style.css 100 B 0 B
build/block-library/blocks/preformatted/style-rtl.css 63 B 0 B
build/block-library/blocks/preformatted/style.css 63 B 0 B
build/block-library/blocks/pullquote/editor-rtl.css 183 B 0 B
build/block-library/blocks/pullquote/editor.css 183 B 0 B
build/block-library/blocks/pullquote/style-rtl.css 316 B 0 B
build/block-library/blocks/pullquote/style.css 316 B 0 B
build/block-library/blocks/query-loop/editor-rtl.css 90 B 0 B
build/block-library/blocks/query-loop/editor.css 89 B 0 B
build/block-library/blocks/query-loop/style-rtl.css 315 B 0 B
build/block-library/blocks/query-loop/style.css 317 B 0 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B 0 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B 0 B
build/block-library/blocks/query-pagination/editor-rtl.css 270 B 0 B
build/block-library/blocks/query-pagination/editor.css 262 B 0 B
build/block-library/blocks/query-pagination/style-rtl.css 168 B 0 B
build/block-library/blocks/query-pagination/style.css 168 B 0 B
build/block-library/blocks/query/editor-rtl.css 814 B 0 B
build/block-library/blocks/query/editor.css 812 B 0 B
build/block-library/blocks/quote/editor-rtl.css 61 B 0 B
build/block-library/blocks/quote/editor.css 61 B 0 B
build/block-library/blocks/quote/style-rtl.css 169 B 0 B
build/block-library/blocks/quote/style.css 169 B 0 B
build/block-library/blocks/rss/editor-rtl.css 201 B 0 B
build/block-library/blocks/rss/editor.css 202 B 0 B
build/block-library/blocks/rss/style-rtl.css 290 B 0 B
build/block-library/blocks/rss/style.css 290 B 0 B
build/block-library/blocks/search/editor-rtl.css 165 B 0 B
build/block-library/blocks/search/editor.css 165 B 0 B
build/block-library/blocks/search/style-rtl.css 342 B 0 B
build/block-library/blocks/search/style.css 344 B 0 B
build/block-library/blocks/separator/editor-rtl.css 99 B 0 B
build/block-library/blocks/separator/editor.css 99 B 0 B
build/block-library/blocks/separator/style-rtl.css 236 B 0 B
build/block-library/blocks/separator/style.css 236 B 0 B
build/block-library/blocks/shortcode/editor-rtl.css 504 B 0 B
build/block-library/blocks/shortcode/editor.css 504 B 0 B
build/block-library/blocks/site-logo/editor-rtl.css 201 B 0 B
build/block-library/blocks/site-logo/editor.css 201 B 0 B
build/block-library/blocks/site-logo/style-rtl.css 115 B 0 B
build/block-library/blocks/site-logo/style.css 115 B 0 B
build/block-library/blocks/social-link/editor-rtl.css 164 B 0 B
build/block-library/blocks/social-link/editor.css 165 B 0 B
build/block-library/blocks/social-links/editor-rtl.css 696 B 0 B
build/block-library/blocks/social-links/editor.css 696 B 0 B
build/block-library/blocks/social-links/style-rtl.css 1.32 kB 0 B
build/block-library/blocks/social-links/style.css 1.32 kB 0 B
build/block-library/blocks/spacer/editor-rtl.css 317 B 0 B
build/block-library/blocks/spacer/editor.css 317 B 0 B
build/block-library/blocks/spacer/style-rtl.css 48 B 0 B
build/block-library/blocks/spacer/style.css 48 B 0 B
build/block-library/blocks/subhead/editor-rtl.css 99 B 0 B
build/block-library/blocks/subhead/editor.css 99 B 0 B
build/block-library/blocks/subhead/style-rtl.css 80 B 0 B
build/block-library/blocks/subhead/style.css 80 B 0 B
build/block-library/blocks/table/editor-rtl.css 478 B 0 B
build/block-library/blocks/table/editor.css 478 B 0 B
build/block-library/blocks/table/style-rtl.css 390 B 0 B
build/block-library/blocks/table/style.css 390 B 0 B
build/block-library/blocks/tag-cloud/editor-rtl.css 118 B 0 B
build/block-library/blocks/tag-cloud/editor.css 118 B 0 B
build/block-library/blocks/tag-cloud/style-rtl.css 94 B 0 B
build/block-library/blocks/tag-cloud/style.css 94 B 0 B
build/block-library/blocks/template-part/editor-rtl.css 557 B 0 B
build/block-library/blocks/template-part/editor.css 556 B 0 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B 0 B
build/block-library/blocks/text-columns/editor.css 95 B 0 B
build/block-library/blocks/text-columns/style-rtl.css 166 B 0 B
build/block-library/blocks/text-columns/style.css 166 B 0 B
build/block-library/blocks/verse/editor-rtl.css 62 B 0 B
build/block-library/blocks/verse/editor.css 62 B 0 B
build/block-library/blocks/verse/style-rtl.css 87 B 0 B
build/block-library/blocks/verse/style.css 87 B 0 B
build/block-library/blocks/video/editor-rtl.css 504 B 0 B
build/block-library/blocks/video/editor.css 503 B 0 B
build/block-library/blocks/video/style-rtl.css 193 B 0 B
build/block-library/blocks/video/style.css 193 B 0 B
build/block-library/common-rtl.css 1.08 kB 0 B
build/block-library/common.css 1.08 kB 0 B
build/block-library/editor-rtl.css 9.54 kB 0 B
build/block-library/editor.css 9.53 kB 0 B
build/block-library/index.js 148 kB 0 B
build/block-library/style-rtl.css 8.85 kB 0 B
build/block-library/style.css 8.85 kB 0 B
build/block-library/theme-rtl.css 736 B 0 B
build/block-library/theme.css 736 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.3 kB 0 B
build/components/style-rtl.css 15.5 kB 0 B
build/components/style.css 15.5 kB 0 B
build/compose/index.js 11.1 kB 0 B
build/core-data/index.js 16.8 kB 0 B
build/customize-widgets/index.js 4.08 kB 0 B
build/customize-widgets/style-rtl.css 168 B 0 B
build/customize-widgets/style.css 168 B 0 B
build/data-controls/index.js 831 B 0 B
build/data/index.js 8.87 kB 0 B
build/date/index.js 31.8 kB 0 B
build/deprecated/index.js 769 B 0 B
build/dom-ready/index.js 576 B 0 B
build/dom/index.js 4.95 kB 0 B
build/edit-navigation/index.js 11 kB 0 B
build/edit-navigation/style-rtl.css 1.26 kB 0 B
build/edit-navigation/style.css 1.25 kB 0 B
build/edit-post/index.js 307 kB 0 B
build/edit-post/style-rtl.css 6.81 kB 0 B
build/edit-post/style.css 6.8 kB 0 B
build/edit-site/index.js 26.4 kB 0 B
build/edit-site/style-rtl.css 4.41 kB 0 B
build/edit-site/style.css 4.41 kB 0 B
build/edit-widgets/index.js 20.2 kB 0 B
build/edit-widgets/style-rtl.css 3.2 kB 0 B
build/edit-widgets/style.css 3.2 kB 0 B
build/editor/editor-styles-rtl.css 543 B 0 B
build/editor/editor-styles.css 545 B 0 B
build/editor/index.js 42.1 kB 0 B
build/editor/style-rtl.css 3.9 kB 0 B
build/editor/style.css 3.9 kB 0 B
build/element/index.js 4.62 kB 0 B
build/escape-html/index.js 735 B 0 B
build/format-library/index.js 6.77 kB 0 B
build/format-library/style-rtl.css 637 B 0 B
build/format-library/style.css 639 B 0 B
build/hooks/index.js 2.28 kB 0 B
build/html-entities/index.js 622 B 0 B
build/i18n/index.js 4.01 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.96 kB 0 B
build/list-reusable-blocks/index.js 3.14 kB 0 B
build/list-reusable-blocks/style-rtl.css 629 B 0 B
build/list-reusable-blocks/style.css 628 B 0 B
build/media-utils/index.js 5.36 kB 0 B
build/notices/index.js 1.86 kB 0 B
build/nux/index.js 3.42 kB 0 B
build/nux/style-rtl.css 731 B 0 B
build/nux/style.css 727 B 0 B
build/primitives/index.js 1.42 kB 0 B
build/priority-queue/index.js 791 B 0 B
build/react-i18n/index.js 1.45 kB 0 B
build/redux-routine/index.js 2.83 kB 0 B
build/reusable-blocks/index.js 3.81 kB 0 B
build/reusable-blocks/style-rtl.css 225 B 0 B
build/reusable-blocks/style.css 225 B 0 B
build/rich-text/index.js 13.5 kB 0 B
build/server-side-render/index.js 2.82 kB 0 B
build/shortcode/index.js 1.7 kB 0 B
build/token-list/index.js 1.27 kB 0 B
build/url/index.js 3.02 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

@gziolo
Copy link
Member Author

gziolo commented Feb 24, 2021

PR in the actual form resolves the issue reported by @jasmussen in #28546:

Screen.Recording.2021-02-24.at.08.12.35.mov

It also works with dynamically registered plugins that declare a corresponding pinned item:

Screen.Recording.2021-02-24.at.08.21.10.mov

@gziolo
Copy link
Member Author

gziolo commented Feb 24, 2021

It looks like the order of fills in the inspector controls is predictable as well:

Screen.Recording.2021-02-24.at.08.37.03.mov

All e2e tests pass.

@youknowriad – what do you think about the changes proposed? The flaw of the previous implementation is that as soon as a fill is unregistered, all other fills get their occurrences value unset. Given that the sort order of fills was based on the value of occurrences then all previously existing fills are automatically moved to the end of the list because any number has higher priority than undefined. I will record another demo for this behavior to better picture it.

Behavior in the main branch:

Screen.Recording.2021-02-24.at.09.41.42.mov

Screen Shot 2021-02-24 at 09 44 15

@gziolo gziolo added the [Feature] Extensibility The ability to extend blocks or the editing experience label Feb 24, 2021
@gziolo gziolo added the [Type] Enhancement A suggestion for improvement. label Feb 24, 2021
@gziolo gziolo self-assigned this Feb 24, 2021
@jasmussen
Copy link
Contributor

Thanks so much for working on this! This is what I see:

bug

This appears to work excellently well for me!

@jasmussen jasmussen requested a review from Copons February 24, 2021 09:20
@gziolo
Copy link
Member Author

gziolo commented Feb 24, 2021

I did another test using the approach from #22027 where slots use bubblesVirtually prop set to true. Code:

diff --git a/packages/interface/src/components/pinned-items/index.js b/packages/interface/src/components/pinned-items/index.js
index 59fc700030..d15602414b 100644
--- a/packages/interface/src/components/pinned-items/index.js
+++ b/packages/interface/src/components/pinned-items/index.js
@@ -7,28 +7,32 @@ import classnames from 'classnames';
 /**
  * WordPress dependencies
  */
-import { Slot, Fill } from '@wordpress/components';
+import {
+	Slot,
+	Fill,
+	__experimentalUseSlot as useSlot,
+} from '@wordpress/components';
 
 function PinnedItems( { scope, ...props } ) {
 	return <Fill name={ `PinnedItems/${ scope }` } { ...props } />;
 }
 
 function PinnedItemsSlot( { scope, className, ...props } ) {
+	const name = `PinnedItems/${ scope }`;
+	const slot = useSlot( name );
+	const hasFills = Boolean( slot.fills?.length );
+
+	if ( ! hasFills ) {
+		return null;
+	}
+
 	return (
-		<Slot name={ `PinnedItems/${ scope }` } { ...props }>
-			{ ( fills ) =>
-				! isEmpty( fills ) && (
-					<div
-						className={ classnames(
-							className,
-							'interface-pinned-items'
-						) }
-					>
-						{ fills }
-					</div>
-				)
-			}
-		</Slot>
+		<Slot
+			{ ...props }
+			name={ name }
+			bubblesVirtually
+			className={ classnames( className, 'interface-pinned-items' ) }
+		/>
 	);
 }

It's better than in the main branch in terms of the order of pinned items that is predictable when registering a new plugin, However, the order changes when unpinning and pinning the sidebar, and goes back to the previous order when refreshing the page:

Screen.Recording.2021-02-24.at.10.19.29.mov

@diegohaz, any hints on how to ensure that the order of fills doesn't change when using bubblesVirtually? As far as I can tell, the biggest difference would be that when it doesn't bubble virtually, we always render the wrapping Fill for the pinned item but it returns null when it's unpinned (disabled) so that's why it is able to retain its order. As discussed with @youknowriad, we would prefer to migrate to bubblesVirtually for the web and leave the old implementation for React Native only wherever possible. Although as presented on the diff above it might be very difficult for the shared code. PinnedItems aren't used in RN code so far.

@gziolo gziolo requested a review from diegohaz February 24, 2021 09:26
@youknowriad
Copy link
Contributor

I guess for bubblesVirtually (portals), React might just append dom nodes when the "children" is not empty, so it seems our only way to keep the order in that case is to have a fallaback that render an empty div or something if the fill renders nothing https://github.com/WordPress/gutenberg/blob/master/packages/components/src/slot-fill/bubbles-virtually/fill.js#L38

@diegohaz
Copy link
Member

diegohaz commented Feb 24, 2021

Maintaining the order of elements with React Portal is tricky. There's a related discussion here: #19242 (comment)

I was experimenting with the key prop here: https://codesandbox.io/s/keeping-react-portal-order-uk9xb?file=/src/App.js

Rendering an empty element as @youknowriad suggested looks like a good alternative too. But I think there will be some styling issues (:last-child and similar) and the Fill component couldn't be omitted for the hidden items.

@gziolo gziolo force-pushed the try/fix-order-fills branch 2 times, most recently from 272d3e5 to 255928e Compare February 25, 2021 06:13
@gziolo gziolo marked this pull request as ready for review February 25, 2021 06:13
@gziolo
Copy link
Member Author

gziolo commented Feb 25, 2021

Rendering an empty element as @youknowriad suggested looks like a good alternative too. But I think there will be some styling issues (:last-child and similar) and the Fill component couldn't be omitted for the hidden items.

I'm afraid there is more. There is logic in many places that render wrappers for the slot if there are no fills rendered. At the moment, when fills render nothing (something falsy like null) they are omitted from the list of fills and we can reliably count the number of fills with content. If we were to use a dummy element, we would need to account for it. For example, in the diff shared in #29287 (comment), the following check would get more complex:

const slot = useSlot( name );
const hasFills = Boolean( slot.fills?.length );

@youknowriad, how about we land this PR with the latest updates that contain unit tests for the slot that doesn't bubble virtually. They clarify how the slot behaves with fills that are always mounted and what happens when a fill gets unmounted.

In the long run, we should try to migrate all slots to bubble virtually as the default for the web, and use the other implementation for the mobile (RN).

Base automatically changed from master to trunk March 1, 2021 15:46
Copy link
Contributor

@gwwar gwwar left a comment

Choose a reason for hiding this comment

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

This is a fun one. I'm going to as a few basic questions, since I'm not familiar with this area. I also had trouble reproducing the original issue in trunk. Did I need to install more than 2 plugins?

@@ -109,19 +108,13 @@ class SlotFillProvider extends Component {
if ( this.slots[ name ] !== slotInstance ) {
return [];
}
return sortBy( this.fills[ name ], 'occurrence' );
Copy link
Contributor

Choose a reason for hiding this comment

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

What problem were we solving earlier that we needed to count instances?

Copy link
Member Author

@gziolo gziolo Mar 2, 2021

Choose a reason for hiding this comment

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

It's very difficult to tell even when you go through past commits and related discussions. There was definitely something that is covered with the unit test that I updated. It was discussed in some of the PRs I studied but there wasn't a clear answer why this particular test needs to be kept:

it( 'should render in expected order', () => {
const { container, rerender } = render(
<Provider>
<div key="slot">
<Slot name="egg" />
</div>
</Provider>
);
rerender(
<Provider>
<div key="slot">
<Slot name="egg" />
</div>
<Filler name="egg" key="first" text="first" />
<Filler name="egg" key="second" text="second" />
</Provider>
);
rerender(
<Provider>
<div key="slot">
<Slot name="egg" />
</div>
<Filler name="egg" key="second" text="second" />
</Provider>
);
rerender(
<Provider>
<div key="slot">
<Slot name="egg" />
</div>
<Filler name="egg" key="first" text="first" />
<Filler name="egg" key="second" text="second" />
</Provider>
);
expect( container ).toMatchSnapshot();
} );

I extended this test to see how it behaves in the trunk branch, and I realized that the test is too specific and doesn't cover more complex cases. The updated version of the test produces the following order that is far from perfect:

<div>
    third
    first (rerendered)
    second
    fourth (new)
</div>

The code of the test:

it( 'should render in expected order when fills unmounted', () => {
const { container, rerender } = render(
<Provider>
<div key="slot">
<Slot name="egg" />
</div>
</Provider>
);
rerender(
<Provider>
<div key="slot">
<Slot name="egg" />
</div>
<Filler name="egg" key="first" text="first" />
<Filler name="egg" key="second" text="second" />
</Provider>
);
rerender(
<Provider>
<div key="slot">
<Slot name="egg" />
</div>
<Filler name="egg" key="second" text="second" />
<Filler name="egg" key="third" text="third" />
</Provider>
);
rerender(
<Provider>
<div key="slot">
<Slot name="egg" />
</div>
<Filler name="egg" key="first" text="first (rerendered)" />
<Filler name="egg" key="second" text="second" />
<Filler name="egg" key="third" text="third" />
<Filler name="egg" key="fourth" text="fourth (new)" />
</Provider>
);
expect( container ).toMatchSnapshot();
} );

Copy link
Contributor

Choose a reason for hiding this comment

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

Curious, if we can't remember, it does make sense to update the expectation here

Copy link
Member

Choose a reason for hiding this comment

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

<div>
    third
    first (rerendered)
    second
    fourth (new)
</div>

@gziolo Does this happen on the main branch or only on this PR? I think rendering Fills conditionally (instead of just omitting its contents) like in that test is a pretty common thing.

Copy link
Member Author

@gziolo gziolo Mar 3, 2021

Choose a reason for hiding this comment

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

@diegohaz, I copied the result from the main branch. I wrongly assumed that it's easy to find the result in the diff section for the same test, that is in this branch:

<div>
     second
     third
     first (rerendered)
     fourth (new)
</div>

It's slightly better as more predictable, but it's still far from perfect. This is also why fills for PinnedItems are optimized to be always rendered, and they conditionally show its content. This way, the order doesn't change in my testing, and as I can tell, it can be confirmed by others.

Aside: It aligns now with the behavior of the version that bubbles virtually, the advantage is that you can keep the fill always rendered. Ideally, we should improve the version that bubbles virtually and use it everywhere. In the meantime, I'm planning to improve the implementation to ensure that mobile (React Natvie) uses the version without portals by design.

@@ -54,6 +55,12 @@ class PluginArea extends Component {
super( ...arguments );

this.setPlugins = this.setPlugins.bind( this );
this.memoizedContext = memoize( ( name, icon ) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this related to ordering, or did we notice a chance to reduce rerenders?

@gziolo gziolo force-pushed the try/fix-order-fills branch from 255928e to 24e7cf9 Compare March 2, 2021 07:03
@gziolo
Copy link
Member Author

gziolo commented Mar 2, 2021

This is a fun one. I'm going to as a few basic questions, since I'm not familiar with this area. I also had trouble reproducing the original issue in trunk. Did I need to install more than 2 plugins?

Yes, it's a very complex piece of software and crucial to how many things are architected 😄 I did most of my tests using wp-env and the test instance of WordPress that you should have at http://localhost:8889. There is a plugin that registers two pinnable sidebars:
Screen Shot 2021-03-02 at 08 18 56

You can also install some plugins like @jasmussen: Jetpack and Yoast. I would say you need at least 3-4 different plugins to notice this behavior.

Copy link
Contributor

@gwwar gwwar left a comment

Choose a reason for hiding this comment

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

Thanks for looking into this @gziolo! Giving tentative approval here, since I didn't spot any issues while smoke testing in a few places. Getting another +1 review for a confidence check would be helpful.

This is a little bit on the riskier side to land, since we don't remember the original reason for counting fill instances. I'm in favor of removing the instance counting if it doesn't actually fix a case. If we decide to land, maybe pick a time where folks can keep an eye on any behavior differences.

@gziolo
Copy link
Member Author

gziolo commented Mar 4, 2021

I'm merging this PR hoping that it will help to better align the behavior between two versions of SlotFill implementation. Let's observe if it doesn't cause any regressions in the block toolbar or block inspector.

Most importantly this PR fixes the issue with the unstable order of pinned items that is very confusing for users as reported by @jasmussen.

@gziolo gziolo merged commit f5b36f3 into trunk Mar 4, 2021
@gziolo gziolo deleted the try/fix-order-fills branch March 4, 2021 10:34
@github-actions github-actions bot added this to the Gutenberg 10.2 milestone Mar 4, 2021
@jasmussen
Copy link
Contributor

Thank you 🚀

@gziolo
Copy link
Member Author

gziolo commented Mar 4, 2021

I left a message on WordPress Slack about this change (link requires registration at https://make.wordpress.org/chat/):
https://wordpress.slack.com/archives/C02QB2JS7/p1614854420062600

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Extensibility The ability to extend blocks or the editing experience [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Pinned items should remember their place, at least the Settings sidebar should
5 participants