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] Portal #1918

Closed
wants to merge 5 commits into from

Conversation

alt1o
Copy link

@alt1o alt1o commented Nov 17, 2023

Portal and components based on Portal, such as Modal / Tooltip / Popover, don't support ssr or ssg or you will encounter react error 418 or 423.
And you can fix this by wrapping this component in a dynamic import. For example, you can use next/dynamic with ssr: false in nextjs. Here is nextjs's official doc: https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#with-no-ssr

  • fix: el isn't removed in react18 strict mode develop environment
  • feat: make className is reactive
  • feat: deprecate style prop, add initStyle instead
  • feat: add basic test cases

* fix: el isn't removed in react18 strict mode develop environment
* feat: make classname is reactive
* feat: depracate `style` prop, add `initStyle` instead
* feat: add basic test cases
@alt1o alt1o changed the title refactor: Portal [refactor] Portal Nov 17, 2023
Copy link

codesandbox-ci bot commented Nov 17, 2023

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit b8638ab:

Sandbox Source
pr-story Configuration
Semi Design: Simple Story Configuration

@DaiQiangReal
Copy link
Collaborator

This PR is expected to be processed this week

@alt1o alt1o requested a review from DaiQiangReal November 24, 2023 03:10
@DaiQiangReal
Copy link
Collaborator

Could you give some details about why and how this PR fixed "el isn't removed in react18 strict mode develop environment" which is descripted at title?

@alt1o
Copy link
Author

alt1o commented Nov 24, 2023

Could you give some details about why and how this PR fixed "el isn't removed in react18 strict mode develop environment" which is descripted at title?

When you develop in react18 strict mode, the constructor and render will be called twice to ensure there is no side effect. So when you open a Modal, the constructor is called twice and el is appended to document.body before it mounts. The following video shows what happened. https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-double-rendering-in-development

So I move appendChild to componentDidMount to make sure el is appended to document.body after it mounts.

11.24.mp4

}
};
const getContainer = this.props.getPopupContainer || context?.getPopupContainer || defaultGetContainer;
const container = getContainer();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since "this.getContainer" is called at constructor and didMount, user's getPopupContainer func is called twice, this may be unexpected to user's will, beacuse user's getPopupContainer may have side effect.

Copy link
Author

Choose a reason for hiding this comment

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

I will change the logic. But we just cannot recognize if user's getPopupContainer returns a valid node. The code could be a little weird.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It doesn't matter, if user return a invalid dom, it's their bug, because the type definetion of getPopupContaienr declear it should return a valid dom.

The problem is we should only execute getPopupContainer once, because user's getPopupContainer may have side effect, for example, user may create a dom and append it to body and return the dom in the getPopupcontainer function, if we execute the function twice after this semi version, user's webpage will have duplicate container.

Copy link
Author

Choose a reason for hiding this comment

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

The best solution, I think, is to make getPopupContainer reactive and mark it a break change🫣

const container = this.getContainer();
if (this.el.parentElement !== container) {
if (container) {
container.appendChild(this.el);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Delaying append "this.el" will cause trobble, since there could be some logic that user may use their real dom in their component or component'children didmount, and since appending "this.el" is delayed, user's code will crash after upgrade semi-ui version.

Copy link
Author

Choose a reason for hiding this comment

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

Indeed, it's a tradeoff, and I'm uncertain about which way to choose.

Copy link
Collaborator

Choose a reason for hiding this comment

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

createPortal will fire all componentDidmount function of arg component's entire react component tree, but if we delay the dom appending operation, the component inside the portal won't have real dom on the page (the dom is created by react but not in the page) even their componentDidupdate executed. And this is very wired.

Copy link
Author

@alt1o alt1o Nov 24, 2023

Choose a reason for hiding this comment

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

I believe the design is sound. React components can be mounted at any node, even if that node isn't attached to a document. The root being attached to a document is no business to react component.

Copy link
Collaborator

@DaiQiangReal DaiQiangReal Nov 24, 2023

Choose a reason for hiding this comment

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

In the former version of semi, we put both createPortal and insert operation all after didMount, but our company user reported that we delay the mount of their component passed to the portal, which will cause their code some bug, so we change into current code.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe the design is sound. React components can be mounted at any node, even if that node isn't attached to a document. The root being attached to a document is no business to react component.

yes

Copy link
Author

Choose a reason for hiding this comment

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

Considering React's substantial emphasis on server components, it's advisable to avoid incorporating side effects within the constructor or render methods. It's conceivable that future versions may discourage or disallow such practices.

Copy link
Author

Choose a reason for hiding this comment

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

It's up to you to choose a proper time to make a break change. I totally understand. 🤠

Copy link
Collaborator

Choose a reason for hiding this comment

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

The server component is in beta for a lone time,and the promoter of this feature have leaved facebook recently, the future of server component is uncertain, we will discuss the pr at a proper time.

@DaiQiangReal
Copy link
Collaborator

The pr will reopen when we find a way to handle all those stuff.

@DaiQiangReal
Copy link
Collaborator

Thanks for your contribution ❤

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