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: Avoid wrapping div for Slot #7231

Closed
wants to merge 2 commits into from
Closed

Conversation

aduth
Copy link
Member

@aduth aduth commented Jun 8, 2018

Closes #6839, #6632.
Supersedes #6920

This pull request seeks to update <Slot /> to avoid the need for a permanent div element wrapper. While a DOM node is needed for virtual-event-bubbling slots, based on the behavior of createPortal, it is acceptable to assign the node as the parent in which the Slot is rendered. This requires a div, albeit temporarily, until the parent can be determined.

Testing instructions:

Verify that there are no regressions in the behavior of Slot/Fill, both with and without bubblesVirtually.

Examples:

  • Plugin sidebar (non-virtual)
  • Popovers (virtual)

cc @ryanwelcher

@aduth aduth added Framework Issues related to broader framework topics, especially as it relates to javascript [Feature] UI Components Impacts or related to the UI component system labels Jun 8, 2018
@aduth aduth requested a review from gziolo June 8, 2018 16:16
}

componentDidMount() {
const { registerSlot = noop } = this.context;

if ( this.props.bubblesVirtually ) {
this.node = this.placeholderRef.current.parentNode;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a little bit concerned about this. What happens if we do:

<div>
    <Slot/>
     <somethingElse />
</div>

What happens when the somethingElse rerenders but not the portal (or the opposite)? I wonder how React handles this and if it's safer to just keep the wrapper for the bubblesVirtually case, do we need to remove it for this use-case as well, it seems the main issue is for Plugin APIs which don't use bubblesVirtually?

@gziolo gziolo added this to the 3.2 milestone Jun 21, 2018
@gziolo gziolo modified the milestones: 3.2, 3.3 Jul 5, 2018
@aduth aduth modified the milestones: 3.3, 3.4 Jul 18, 2018
@pento pento modified the milestones: 3.4, 3.5 Jul 30, 2018
@gziolo gziolo modified the milestones: 3.5, 3.6 Aug 8, 2018
Copy link
Member

@gziolo gziolo left a comment

Choose a reason for hiding this comment

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

It needs to be rebased if we want to land it.

I don't have any feedback to share so the question is if we want to address what @youknowriad raised and merge it?

In my opinion, we should decide whether we land it in 3.6 as proposed or leave it as is forever 😄

@@ -16,6 +16,15 @@ import Provider from '../provider';
*/
import { Component } from '@wordpress/element';

// TODO: Avoid mock when Enzyme supports React 16.3.0+ Fragment. May require
Copy link
Member

Choose a reason for hiding this comment

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

We probably should use react-test-renderer instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

And here I thought we stopped using Enzyme altogether 😬

@gziolo gziolo added the Needs Decision Needs a decision to be actionable or relevant label Aug 13, 2018
@aduth
Copy link
Member Author

aduth commented Aug 13, 2018

@gziolo Guessing you stopped by on an audit of API Freeze. FWIW I don't know that this really has any impact on public-facing API, except for how one might expect to interact with styling (and the presence of intermediary div elements).

@gziolo
Copy link
Member

gziolo commented Aug 14, 2018

Guessing you stopped by on an audit of API Freeze. FWIW I don't know that this really has any impact on public-facing API, except for how one might expect to interact with styling (and the presence of intermediary div elements).

Yes, this exactly what happened. I wanted to clean up this project to see where we are. I agree it can be considered as non-breaking from plugin developer's standpoint. However, it might cause subtle unexpected visual discrepancies for theme developers. It's really your call. I'm fine with giving it a lower priority and removing it from the API Freeze project.

@youknowriad
Copy link
Contributor

Do you think we can move this forward @aduth? I need this in one of the UI refactorings of the block toolbars.

Also, do you think an acceptable compromise here would be to only drop the wrapper when bubblesVirtually is false to avoid this #7231 (comment)
?

@aduth
Copy link
Member Author

aduth commented Aug 24, 2018

I think I share your concern at #7231 (comment) . I don't like fragmenting the behavior based on bubblesVirtually, but it might be the only / best option in this case.

I'll plan to do some short experiments on whether the React reconciler can deal with slot siblings, but otherwise will move forward with the Fragment for non-bubblesVirtually.

@aduth
Copy link
Member Author

aduth commented Aug 24, 2018

I've spent the better part of my afternoon on this one. Aside from porting the tests to using react-test-renderer, which was successful, I'm running into a few new frustrations, not entirely related to what's going on here:

There's a bug with virtual portal rendering, even with just a basic test case (fails on master as well):

	it( 'should render virtually by portal', () => {
		const tree = renderIntoDocument(
			<Provider>
				<Slot name="chicken" bubblesVirtually />
				<Fill name="chicken">
					content
				</Fill>
			</Provider>
		);

		const slot = findRenderedComponentWithType( tree, Slot );
		const slotDiv = findRenderedDOMComponentWithTag( slot, 'div' );
		expect( slotDiv.outerHTML ).toBe( '<div role="presentation">content</div>' );
	} );

It's related to some other issues we've had with race conditions between the rendering and registration of a fill/slot.

Generally, I'm finding this interchange between slots and fills, and the registration and rendering between them to be quite fragile. This is compounded by the distinct behaviors of bubblesVirtually and not. There's a lot in SlotFillProvider where we're just calling forceUpdate often unnecessarily, but in a (failed) effort to try to remedy these race conditions.

I considered more whether the bubblesVirtually is something we still want to offer as an option, or if we should default to one or the other. Based on some usage I found, I expect we are certainly still in need of the bubblesVirtually behavior (see e.g. Autocomplete capturing Popover input via onInput).

Given the prior concern about React's inability to reconcile changes if we'd render into a Slot by its parentNode (which I confirmed to be a valid concern), we still need a wrapper element for bubblesVirtually which would defeat the purpose of the pull request if we were to assign as the only option. There are also some issues I recall with bubblesVirtually not being the desired intended behavior (like onMouseDown for block multi-selection triggering when interacting with a block's inspector controls like a Range).

So I'm left feeling we need bubblesVirtually as an option. Considering how we could make this less fragile, I contemplated as part of this a refactor of Slot / Fill using the newer context API. Perhaps having the SlotFillProvider manage fills and slots via proper state (trickling down to consumers) could provide a more solid basis for keeping the slots and fills better in sync.

The downside is that in the course of trying various refactors, I've come to the conclusion that our tests do not cover the edge cases of the race conditions very well, so I have little confidence in our ability to refactor without potential for breakage.

Finally, there's some "fun" side-effects of removing the Slot wrapper, including a regression in the block inspector which strips intended padding from the block title:

image

...because we've styled elements in the sidebar to rely on the wrapper being an indicator of things like :first-child pseudo-class:

image

&:first-child {
margin-top: 16px;
}

I've pushed a rebase of the branch which passes all tests to my knowledge, and "works", but I find to be very fragile and unreliable.

@youknowriad
Copy link
Contributor

I agree that slot/fill is still fragile in general and us trying to support two different implementations in the same components makes it even more fragile.

  • I wonder if we could make them separate components, it would make them easier to reason about
  • I wonder if we can avoid using Slot/Fill in Popover and Autocomplete in favor of Portals but without necessarily having to rely on Slot and Fill (right in the components).

@youknowriad youknowriad modified the milestones: 3.7, 3.8 Aug 30, 2018
@gziolo
Copy link
Member

gziolo commented Sep 5, 2018

Can we rebase and get it in as is in 3.8?

@gziolo gziolo added the [Type] Breaking Change For PRs that introduce a change that will break existing functionality label Sep 5, 2018
@youknowriad
Copy link
Contributor

Actually, I forgot to mention but I removed the container div in #9572 it's not perfect and we probably need more robust tests.

@aduth
Copy link
Member Author

aduth commented Sep 5, 2018

Most of the changes in this pull request are an improvement on what exists in master. My previous comment applies to issues which were discovered in the course of refactoring, but that also exist in master as well. So there's an option that we just merge the first imperfect pass and open a separate pull request to address the remaining issues. However, with #9572 making the pivotal change already, this pull request is already mostly "refactoring" anyways (with a few small improvements), so it could be repurposed to attempting to address the remaining concerns.

@youknowriad I don't see the issue on master, so I'm assuming the styling issues noted in #7231 (comment) were accounted for ?

@gziolo gziolo removed the [Type] Breaking Change For PRs that introduce a change that will break existing functionality label Sep 5, 2018
@gziolo gziolo removed this from the 3.8 milestone Sep 5, 2018
@gziolo
Copy link
Member

gziolo commented Sep 5, 2018

I removed it from API freeze project as this PR won't have any breaking changes. #9572 might produce some styling issues if some theme used those div wrappers to style things.

@youknowriad
Copy link
Contributor

@youknowriad I don't see the issue on master, so I'm assuming the styling issues noted in #7231 (comment) were accounted for ?

Yes, I did fix this issue

@aduth
Copy link
Member Author

aduth commented Mar 13, 2019

This may still be something worth exploring, but the pull request has languished too far and may be too difficult to salvage in its current form.

@aduth aduth closed this Mar 13, 2019
@aduth aduth deleted the remove/slot-div branch March 13, 2019 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] UI Components Impacts or related to the UI component system Framework Issues related to broader framework topics, especially as it relates to javascript Needs Decision Needs a decision to be actionable or relevant
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants