preactjs / preact-compat

ATTENTION: The React compatibility layer for Preact has moved to the main preact repo.
http://npm.im/preact-compat
MIT License
949 stars 148 forks source link

Implement createPortal API #452

Closed evan-scott-zocdoc closed 6 years ago

evan-scott-zocdoc commented 6 years ago

https://reactjs.org/docs/portals.html

developit commented 6 years ago

Likely doable via preact-portal

jeberly commented 6 years ago

This is causing problems when trying to leverage https://ant.design/

jachicao commented 6 years ago

Also material-ui

developit commented 6 years ago

This is #1 on the todo list.

ducwings commented 6 years ago

also in 'reactstrap` (beta)


61:13-25 "export 'createPortal' was not found in 'react-dom'
 @ ./node_modules/react-portal/es/Portal.js
 @ ./node_modules/react-portal/es/PortalCompat.js
 @ ./node_modules/react-portal/es/index.js
 @ ./node_modules/reactstrap/dist/reactstrap.es.js```
sammoore commented 6 years ago

I took a swing at implementing this (very naively), but no success; render fails with <undefined></undefined> output. I'm going to take a deeper look and also set up a smaller project to reproduce.

If there's anyway to get a stack trace on an undefined tag, that would be helpful, but I assume the only reason that's not happening is because the root of the issue comes from the custom code in preact-compat that I've added.

marvinhagemeister commented 6 years ago

When you enable the debug tools you have callback where you can make custom assertions on each vnode object. It has a check if the tag is undefined already built-in. That should help :)

eveningkid commented 6 years ago

So actually, quick question: is it only related to preact-compat or preact itself? I'm still thinking about switching to preact as the error shows up because of antd package in my case.

Would the same error show up without using the compatibility layer but preact directly?

developit commented 6 years ago

@samtheprogram hiya! Mind joining our chat? https://preact-slack.now.sh

sammoore commented 6 years ago

Hey @developit, I've joined under the same username as my GitHub account!

ev-dev commented 6 years ago

Also breaks compatibility with searchkit (elasticsearch component library) with the same error of export createPortal not found in react-dom. I assume all use of the Portal API is not currently available

Madhu0 commented 6 years ago

I was trying to achieve something similar to createPortal using render method. But i am unable to pass context to the child components. Here is what i am doing..

class BaseModal extends Component {

    constructor(props) {
        super(props);
        this.id = (new Date()).getTime() + '';
        this.element = '';
    }

    componentDidMount() {
        console.log('In mount');
        const { children } = this.props;
        this.element = render(<div id={this.id}>{children}</div>, document.getElementById('modal-div'));
    }

    componentDidUpdate() {
        const el = document.getElementById(this.id);
        el.parentElement.removeChild(el);

        const { children } = this.props;
        this.element = render(<div id={this.id}>{children}</div>, document.getElementById('modal-div'));
    }

    componentWillUnmount() {
        console.log('In unmount');
        const el = document.getElementById(this.id);
        el.parentElement.removeChild(el);
    }

    render() {
        console.log(this.context);
        // const childrenWithPrpos = children.map((child, index) => cloneElement(child, this.props));
        return '';
    }
}
class Test extends Component {
    render(){
        return(<BaseModal>{this.props.children}</BaseModal>)
    }
}

Here context is not passed to children, as it is not rendered as a child.

Kanaye commented 6 years ago

Hey @Madhu0. I updated your example code to use a wrapper component to pass the context. You find it [here](https://preactjs.com/repl?code=const%20%20%7B%20h%2C%20render%2C%20Component%20%7D%20%3D%20preact%3B%0A%0Aclass%20ContextProvider%20extends%20Component%20%7B%0A%09getChildContext()%20%7B%0A%09%09return%20this.props.context%3B%0A%09%7D%0A%09render()%20%7B%0A%09%09return%20this.props.children%20%20%26%26%20this.props.children%5B0%5D%3B%0A%09%7D%0A%7D%0A%0Aclass%20BaseModal%20extends%20Component%20%7B%0A%0A%09renderChildren()%20%7B%0A%09%09return%20(%0A%09%09%09%3Cdiv%20id%3D%7Bthis.id%7D%3E%0A%09%09%09%09%3CContextProvider%20context%3D%7Bthis.context%7D%3E%0A%09%09%09%09%20%20%7Bthis.props.children%7D%0A%09%09%09%09%3C%2FContextProvider%3E%0A%09%09%09%3C%2Fdiv%3E%0A%09%09)%3B%0A%09%7D%0A%0A%09constructor(props)%20%7B%0A%09%09super(props)%3B%0A%09%09this.id%20%3D%20(new%20Date()).getTime()%20%2B%20%27%27%3B%0A%09%09this.element%20%3D%20%27%27%3B%0A%09%7D%0A%0A%09componentDidMount()%20%7B%0A%09%09const%20%7B%20children%20%7D%20%3D%20this.props%3B%0A%09%20%20this.target%20%3D%20document.querySelector(this.props.target)%3B%0A%09%09this.element%20%3D%20render(this.renderChildren()%2C%20this.target)%3B%0A%09%7D%0A%0A%09componentDidUpdate()%20%7B%0A%09%09this.element%20%3D%20render(this.renderChildren()%2C%20this.target%2C%20this.element)%3B%0A%09%7D%0A%0A%09componentWillUnmount()%20%7B%0A%09%09const%20element%20%3D%20this.element%3B%0A%09%09if%20(element%20%26%26%20element.parentElement)%20%7B%0A%09%09%09element.parentElement.removeChild(this.element)%3B%0A%09%09%7D%0A%09%7D%0A%0A%09render()%20%7B%0A%09%09return%20%3Cdiv%20%2F%3E%3B%0A%09%7D%0A%7D%0A%0Aclass%20Test%20extends%20Component%20%7B%0A%09%09getChildContext()%20%7B%0A%09%09%09return%20%7B%0A%09%09%09%20some%3A%20%22Context%22%0A%09%09%09%7D%3B%0A%09%09%7D%0A%20%20%20%20render()%7B%0A%20%20%20%20%20%20%20%20return(%3CBaseModal%20target%3D%22%23mymodal%22%3E%7Bthis.props.children%7D%3C%2FBaseModal%3E)%0A%20%20%20%20%7D%0A%7D%0A%0Aclass%20ContextPrinter%20extends%20Component%20%7B%0A%09render()%20%7B%0A%09%09return%20(%0A%09%09%09%3Cpre%3E%0A%09%09%7BJSON.stringify(this.context)%7D%0A%09%09%09%3C%2Fpre%3E%0A%09%09)%3B%0A%09%7D%0A%7D%0A%0Aconst%20App%20%3D%20()%20%3D%3E%20(%0A%09%3Cdiv%3E%09%09%0A%09%09%3Cdiv%20id%3D%22mymodal%22%20style%3D%22border%3A%201px%20solid%20%23000%3B%22%3E%3C%2Fdiv%3E%0A%09%09%3Cp%3ESome%20content%20asdf%3C%2Fp%3E%0A%09%09%3CTest%3E%0A%09%09%09%3CContextPrinter%20%2F%3E%0A%09%09%3C%2FTest%3E%0A%09%3C%2Fdiv%3E%0A)%3B%0A%0Aexport%20default%20App%3B) in the preact repl.

Madhu0 commented 6 years ago

Hey @Kanaye, this works. Thank you for your time.

If we can provide props and context to the component which will be mounted somewhere outside our actual app, aren't we achieving everything React.createPortal does? am i missing something?

I am trying to understand the portal implementation in react, i've gone through createPortal source code and i wasn't able to understand completely. But the basic idea is, it should work just like any other component inside our app, right?

We can even use render(App, target, previousApp) to find the diff in DOM and update, instead of deleting it and rendering it again.

developit commented 6 years ago

@Madhu0 around 90% of it, yes. They also redirect event bubbling through portal boundaries, which Preact is unlikely to ever do.

ar1a commented 6 years ago

This is causing an issue with react-md,

./node_modules/react-md/es/Helpers/Portal.js
11:23-35 'react-dom' does not contain an export named 'createPortal'.
armand1m commented 6 years ago

is there any thing we can do while this is not implemented?

mozillo commented 6 years ago

Same issue with react-select

'react-dom' does not contain an export named 'createPortal'
developit commented 6 years ago

If anyone wants to PR the implementation, something like this would work:

import { h } from 'preact';
import Portal from 'preact-portal';

export function createPortal(child, container) {
  return <Portal into={container}>{child}</Portal>
}
developit commented 6 years ago

nevermind, there was a much smaller and simpler way and I've implemented it in #486. If anyone has time to test it out that would be lovely.

29rayb commented 6 years ago

I'm getting the same error about 'createPortal' above. I know it's not yet released, but I've copied the updates for 'createPortal' from https://github.com/developit/preact-compat/blob/master/src/index.js but am still getting this error:

129:12-33 "export 'createPortal' (imported as 'ReactDOM') was not found in 'react-dom'

What am I missing?