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

Refactor Html with portal #1465

Merged
merged 1 commit into from
Aug 21, 2023
Merged

Refactor Html with portal #1465

merged 1 commit into from
Aug 21, 2023

Conversation

axelboc
Copy link
Contributor

@axelboc axelboc commented Aug 9, 2023

Context

Html allows rendering HTML elements from inside React Three Fiber's renderer. It works by appending a div to the DOM and rendering its children into it with ReactDOM.render(). The div is appended to r3fRoot by default, to canvasWrapper when overflowCanvas is set, or to a custom container when container is set.

The problem currently is that the children of Html always end up wrapped in an extra div. This is a bit annoying when debugging the DOM and it's quite limiting as the extra div can make styling cumbersome and can lead to invalid HTML (e.g. div > svg). We worked around this extra div before in FloatingControl and SvgElement by rendering the children into another container through a portal.

The container prop has other issues, like the fact that it has no effect when overflowCanvas is set or that it's buggy with dynamically rendered containers (i.e. async refs) – the children get rendered into r3fRoot first until the container becomes available.

Proposed changes

In this PR, I refactor Html to render its children through a portal, thus removing the extraneous div. The children now end up directly under r3fRoot or, if overflowCanvas is set, under canvasWrapper.

To avoid the pitfall of dynamically rendered containers and keep Html as light as possible, I remove the container prop altogether. Equivalent behaviour can be achieved by the consumer of the Html component with a portal (like in FloatingControl and SvgElement). Yes, in this case, it's basically portal-ception, since a portal is rendered inside another portal, but my quick tests and research don't seem to point to any negative performance implications.

So not much changes in practice:

  • if an element needs to go into r3fRoot or canvasWrapper, we still use <Html> or <Html overflowCanvas>;
  • if multiple elements need to go into the same custom container, we still use <Html>{createPortal(..., container)}</Html>.

I took the opportunity to improve the documentation of Html in the Storybook:

image

image

image

packages/lib/src/vis/shared/Html.tsx Show resolved Hide resolved
packages/lib/src/vis/shared/Html.tsx Show resolved Hide resolved
packages/lib/src/vis/shared/VisCanvas.tsx Outdated Show resolved Hide resolved
@axelboc axelboc requested a review from loichuder August 9, 2023 09:43
@axelboc axelboc force-pushed the html-portal branch 2 times, most recently from dfc3059 to 633f88f Compare August 10, 2023 08:47
@axelboc axelboc removed the request for review from loichuder August 10, 2023 11:54
@axelboc axelboc marked this pull request as draft August 10, 2023 11:54
@axelboc axelboc changed the base branch from main to event-bubbling August 17, 2023 08:42
@axelboc axelboc marked this pull request as ready for review August 17, 2023 08:44
@axelboc axelboc requested a review from loichuder August 17, 2023 08:44
@axelboc
Copy link
Contributor Author

axelboc commented Aug 17, 2023

One alternative I considered to solve the problem of conditionally-rendered containers is to keep the container prop but to change its type to HTMLElement | null | undefined. This would allow passing null to indicate that a container is expected but not yet available, thus allowing Html to not render its children until the container becomes available.

The downside of this solution is that it doesn't actually prevent consumers from hitting the initial problem if they mistakenly store their container in a state with type HTMLElement | undefined... By forcing them to render their own portal, they are required to check that their container exists before calling createPortal, so it doesn't matter if the initial value is undefined or null.

Copy link
Member

@loichuder loichuder left a comment

Choose a reason for hiding this comment

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

Good thinking 👍

Base automatically changed from event-bubbling to main August 21, 2023 13:55
@axelboc axelboc merged commit 1f8bb6f into main Aug 21, 2023
8 checks passed
@axelboc axelboc deleted the html-portal branch August 21, 2023 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants