everweij / react-laag

Hooks to build things like tooltips, dropdown menu's and popovers in React
https://www.react-laag.com
MIT License
907 stars 47 forks source link

[BUG] using react-laag in a child window via React portals renders in the parent window #97

Open scarroll2 opened 1 year ago

scarroll2 commented 1 year ago

Describe the bug My scenario is that I was opening a React portal into a new browser window. In this new window a component uses react-laag (via useLayer) to render dropdown options. This initially renders the react-laag later in the parent (opening) browser window instead of the new (child) window.

I expected passing a reference to the child window via the environment prop to just "work" out of the box.

When this did not, I used the container function to dynamically create a root div inside the child window.

This still didn't work because, for some reason, calling instanceof HTMLElement from the parent window's JavaScript context returned false on the HTMLElement created in the child window and thus, this error was thrown:

react-laag: You've passed a function to the 'container' prop, but it returned no valid HTMLElement

As a work-around I was able to set the prototype of the newly created div like so:

Object.setPrototypeOf(container, HTMLElement.prototype);

To Reproduce Steps to reproduce the behavior:

  1. Create a new window reference using window.open(...) and set it as the target to ReactDOM.createPortal.

    const newWindow = window.open(
    undefined,
    undefined,
    'toolbar=no, location=no, directories=no, status=no, menubar=no'
    );
  2. In the react tree within the portal render a component that uses useLayer

    const portal = ReactDOM.createPortal(
    <WrapperComponent
        childWindow={newWindow}
    />,
    newWindow.document.body
    );
  3. Use an implementation as described above to dynamically create a container in the child window and pass it to the container factory function in useLayer

    
    // note this is simplified and just for illustration

import { useLayer } from "react-laag"; interface Props { childWindow: Window; }

function WrapperComponent({childWindow}: Props){ const { renderLayer } = useLayer({ environment: childWindow, container: () => createDivIn(childWindow) }) return (

{renderLayer(
Content goes here!
)}
);

}



4. See error

**Expected behavior**
It would be great if setting the "environment" (or some other prop) allowed the `react-laag` container to "just work" and render the container into the child window without needing to dynamically create a container in the child window and pass it into `container`.

That said, I would expect a `div` created via a standard `window`'s `document` API to work when passed into the `container` function without the need to  override its prototype.

**Browser / OS:**
 - OS: mac os
 - Browser: chrome
 - Version: 117.0.5938.62

**Additional context**
I know this is a pretty extreme edge-case, but others might run into this problem and it was not very easy to diagnose.

If nothing else, it would be helpful to document this as a known "gotcha" and to list the workaround.

Thanks!