Giter Club home page Giter Club logo

react-reverse-portal's People

Contributors

adri1wald avatar antoinetissier avatar pimterry avatar renatasva avatar robrendell avatar spautz avatar spong avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-reverse-portal's Issues

iframes reload from source

Hi! Really cool library, thanks for building it.

I've found though that if I use an iframe to render an embedded video, changing the parent will reload the embed from source each time. The actual source URL of the iframe doesn't matter. Do you have any idea why this might be the case?

Dependency array for useMemo example

Should the useMemo example have an empty array as the second parameter?

const portalNode = React.useMemo(() => portals.createPortalNode(), []);

Thanks!

Queries around library usage

Can we use this library in production level code or there are some limitations which should be highlighted ?

I have following queries around it though :-

  1. New parent would be able to completely control InPortal component via props & refs
  2. InPortal component would get new props in current render cycle of the parent without any delay
  3. Any case/scenario where teleporting components via react-reverse-portal won't work :)
  4. Event propagation happens w.r.t to the new parent in OutPortal or the detached node where InPortal component is mounted

@pimterry ^^

Rendering portal in a new window causes error: "html" portalNodes must be used with html elements, but OutPortal is within DIV

I don't know if this is an issue I should raise with you, or the react-new-window maintainers, but I'll start here.

I'm using react-reverse-portal along with react-new-window to make a pop-out window... initially the portal is rendered inside a div with window-like chrome to allow dragging and resizing, but the user can click a "pop out" button to have the portal in an actual window outside their browser.

This used to work, but after upgrading react-new-window from version 0.1.2 to 0.1.3 I started to get the error "html" portalNodes must be used with html elements, but OutPortal is within <DIV>

It's correct, the parent of the OutPortal inside the new window is a DIV, but that doesn't seem to be a problem - nearly every example of react-reverse-portal puts the OutPortal in something other than the root of the page, and they don't complain. So, while the fix for me is to downgrade react-new-window, I'm uncertain why react-reverse-portal is complaining.

Here's a codesandbox demonstrating the issue: https://codesandbox.io/s/happy-benji-20o5q?file=/src/App.js

It won't work due to cross-origin issues inside the codesandbox editor, but you if open the page in a new window (or navigate directly to https://20o5q.csb.app/ ) you can press the "pop out" button and observe the issue. If you downgrade react-new-window to 0.1.2, the codesandbox will start to work as expected.

Thanks!

Giving style to intermediary div

Hi, thanks for your lib it's been super helpful for us.
We've been using it to render a 3D app in different pages of our react application.

However for this to work well, we need the 3D to take 100% of the space of its container. This is broken in reverse-portal because of the intermediary div you create (document.createElement('div')), which does not have any style, classname, or way to inject those.
We've been using nth-child(1) in React as a work around, but moving to preact, it seems it's not always reliable (I believe sometimes the placeholder div remains in the DOM). Could you add a way for us to inject either a style or class name to that div?

Nextjs error

Has anyone tried to run this on Nextjs? Getting an error:

"Cannot read property 'getInitialPortalProps' of null"

Props stay the same when InPortal content is hidden

We use an Intersection Observer to determine whether a video is in view and to render it. The problem arises when the video leaves all OutPortals: it maintains the props that it received before going back 'into hiding', so inView prop never gets set to false and

Is there a way to force the detached component to revert back to default props whenever it is hidden? Or some sort of workaround? I'm just looking for any way for the component to have state that triggers when it is detached from the DOM.

OutPortal componentDidUpdate doesn't work

Hello, thank you for the library, it saves me now.

I'm using it for the audio widget component which should be started at the audio page and then be available for the user while they're browsing through the website.

I had one error which took me some time to figure out how to resolve. I did a deep dive into the component's functionality and wanted to share the insight, so next time someone else experiencing it could save their time.

Error

The issue I've had is that I needed to re-create the node prop based on the id I have, but OutPortal's componentDidMount was failing because of the error being thrown at the .mount() method:

Cannot read property 'replaceChild' of null

https://github.com/httptoolkit/react-reverse-portal/blob/master/src/index.tsx#L47-L50

Usage

So here is how I was using the react-reverse-portal:

const MyComponent = () => {
  const { setPortalNode } = usePortalNodeReference();
  const { Id, asWidget } = useDataState();
  const portalNode = useMemo(() => createPortalNode(), [Id]);
  setPortalNode(portalNode);

 return  (
    <>
      <InPortal node={portalNode}><AudioComponent /></InPortal>
      {asWidget &&  <OutPortal name="widget" node={portalNode} Id={Id}></OutPortal>}
    </>
  );
}

const AnotherComponent = () => {
  const { Id, asWidget } = useDataState();
  const { portalNode } = usePortalNodeReference();
  return !asWidget && <OutPortal name="page" node={portalNode}/>
}

Every time, portalNode or Id prop was updating, OutPortal would call the .mount() and it would throw an error.

I debugged it and this is because OutPortal's placeholder reference is missing the parentNode once .replaceChild was called on it, and then after the update, it tries to use the same placeholder's parentNode again in the mount assuming that it is present:

https://github.com/httptoolkit/react-reverse-portal/blob/master/src/index.tsx#L148

I cannot tell why exactly the placeholderNode doesn't get re-rendered on prop change and get a parent node again, but I feel is because of .replaceChild() method call at .mount() is breaking react's virtual dom representation.

At the end of the day I came to the solution where I do not re-create portalNode, but use key prop to force re-mount OutPortal and AudioComponent when the Id prop changes:

const MyComponent = () => {
  const { setPortalNode } = usePortalNodeReference();
  const { Id, asWidget } = useDataState();
-  const portalNode = useMemo(() => createPortalNode(), [Id]);
+  const portalNode = useMemo(() => createPortalNode(), []);
  setPortalNode(portalNode);

 return  (
    <>
-      <InPortal node={portalNode}><AudioComponent /></InPortal>
+     <InPortal node={portalNode}><AudioComponent key={Id} /></InPortal>
-      {asWidget &&  <OutPortal name="widget" node={portalNode} Id={Id}></OutPortal>}
+     {asWidget &&  <OutPortal key={Id} name="widget" node={portalNode} Id={Id}></OutPortal>}
    </>
  );
}

Outcome

Having all of this information, I think it is sensible to have a check for placeholders parent node before using it, also react-reverse-portal could give component's user a warning with an explanation of what is happening.

Migrate from Travis to Github Actions?

It looks like this repo is still using travis-ci.org for CI, which is deprecated and slated for shutdown at some point.

Migrating to travis-ci.com is an option, but personally I've found Github Actions to be a nicer experience. If it sounds worthwhile and desirable, I'd be happy to submit a PR to migrate from Travis to Github Actions -- likely a diff similar to https://github.com/spautz/limited-cache/pull/38/files

Or if some other CI system seems better, it may be worth looking at before Travis kills off their .org service

How to *destroy* a portal?

Portals are stored in the fiber context, as far as I can tell, and InPortal creates a portal, which is then saved to fiber (through functional component return statement). Such registration (context) is required to activate the portal.

containers are replaced automatically by react-reverse-portal, but how to destroy the portal itself?

Although containers can be removed with removeChild, I don't see a way to destroy the portal itself.

Any clues?

I'm trying to use portals in a virtual scroller (long story), so there's quite an active churn of portals.

Cannot add inline styles to the container div

Hi,I really appreciate your work, it soved my problem, but it does not full up the parent of the component, what I did was just add a line 61, the code looks like this:
if (elementType === ELEMENT_TYPE_HTML) {
element= document.createElement('div');
element.style.cssText = 'width:100%;height:100%;';
}

I do not know whether I have changed your code in the right way or in a wrong way, looking forward to your reply,

with many thanks!

Text field autoFocus doesn't work in a portal

If you have a text field in a portal, the autoFocus attribute doesn't work. The text field isn't automatically focused.

Is this a bug, or is this expected to happen? In the latter case, is there a workaround?

https://codesandbox.io/s/wonderful-wilbur-4niwtl?file=/src/App.js

import {createHtmlPortalNode, InPortal, OutPortal} from 'react-reverse-portal';

export default function App() {
  const node = createHtmlPortalNode();
  return (
    <div className="App">
      <InPortal node={node}>
        <input type="text" autoFocus />
      </InPortal>
      <OutPortal node={node} />
    </div>
  );
}

Add ES5 distribution?

Hey @pimterry, came across this lib in the comments of facebook/react#13044, and it has been just the thing we needed to get react-beautiful-dnd draggables working from within other react component trees. So thanks for taking the time to put this together. ๐Ÿ™‚

Anyway, as unfortunate as it is, our builds need to support IE11 for just a little while longer and so we need an ES5 distribution to do that.

I'm not sure if this is something you're open to including as it adds additional bloat and very few people need it, but I figured I'd ask. I've got this working over on my fork (spong@bddc939) and am happy to open a PR, but just wanted to create an issue to get your feedback first.

If this isn't something you're interested in supporting, no worries at all. We can add the appropriate attribution and build from source on our end in this interim period while we still have to support IE11.

Thanks again!

Does not work with SVGs

It looks like the OutPortal wraps the content in a <div>, which results in the content not actually being rendered if it is used within a <svg> since divs cant exist in svgs.

It would be nice if you could specify an optional component prop or something to be used a wrapper for this render. If it was wrapped in a <g> for example, I imagine it would work fine.

Example that doesn't actually work:

import React, { FC, useMemo } from "react";
import { createPortalNode, InPortal, OutPortal } from "react-reverse-portal";

const Test: FC = () => {
  const portal = useMemo(() => createPortalNode(), []);

  return (
    <div>
      <svg>
        <rect x={0} y={0} width={300} height={50} fill="gray"></rect>
        <rect x={0} y={50} width={300} height={50} fill="lightblue"></rect>
        <svg x={30} y={10}>
          <InPortal node={portal}>
            <text alignmentBaseline="text-before-edge" fill="red">
              test
            </text>
          </InPortal>
        </svg>
        <svg x={30} y={70}>
          <OutPortal node={portal} />
        </svg>
      </svg>
    </div>
  );
};

export default Test;

Seems reverse portals are not passing events up the tree

If I have some eg onClick events on component inside portal - this event will not be detected if used with reverse portal.

Eg. InPortal is rendered at the root level of the app

Then OutPortal is used inside some container that has it's own click events.

In such case - click events are only passed up directly to the root element, skipping container of OutPortal.

Race condition when swapping nodes between <OutPortal/>s

Problem

Hi, I want to use this library to be able to move some components in a layout, without having to unmount/remount them. However, I am unable to do so because of race conditions when swapping nodes betwing two <OutPortal/>s within a single rerender.

Cause

Here is a very simplified example that shows what happens:

Suppose I have two nodes NodeA and NodeB created once and for all.

Now suppose down the line we use those nodes in a layout, that would look like this at the first render.

<OutPortal node={nodeA}/> // let's call it OutPortal1, and its placeholder Placeholder1
<OutPortal node={nodeB}/> // let's call it OutPortal2, and its placeholder Placeholder2

Then, suppose an update arrives, yielding the following instead:

// nodeA and nodeB have been swapped.
<OutPortal node={nodeB}/>
<OutPortal node={nodeA}/>

Here is roughly what happens when going through the code of react-reverse-portal:
OutPortal1 componentDidUpdate is triggered, with nodeB instead of nodeA

  • unmount nodeA
    • OutPortal1.child = Placeholder1
    • nodeA.parent = undefined
  • mount nodeB in OutPortal1
    • unmount nodeB
      • OutPortal2.child = Placeholder2
      • nodeB.parent = undefined
    • OutPortal1.child = nodeB
    • nodeB.parent = OutPortal1

OutPortalB componentDidUpdate is triggered with nodeA instead of nodeB

  • unmount nodeB (at this point nodeB.parent is OutPortal1)
    • OutPortal1.child = Placeholder2
    • nodeB.parent = undefined
  • mount nodeA in OutPortal2
    • unmount nodeA => nothing happens since at this point nodeA.parent is undefined
    • OutPortal2.child = nodeA
    • nodeA.parent = OutPortal2

Because of this race condition, we end up having OutPortal1 yielding Placeholder1 and OutPortal2 yielding NodeA, even though we just wanted to swap the nodes.

Note: this is a very simplified example, but my real use-case involves a recursive layout with potentially many content nodes.

Question

Are there any plans on handling this use case ? Can you think of a workaround ?

Doesn't work when there is nested `portals.OutPoral`

Here is my code

<div>
  <portals.InPortal node={colorPortalNode}>
    <ColorProvider />
  </portals.InPortal>
  <portals.InPortal node={counterPortalNode}>
    <CounterProvider />
  </portals.InPortal>
  <portals.OutPortal node={colorPortalNode}>
    <portals.OutPortal node={counterPortalNode}>
      <button onClick={() => setPage((prev) => (prev + 1) % pages.length)}>
        Change Page
      </button>
      {pages[page] === "counter" && <Counter />}
      {pages[page] === "color" && <Color />}
    </portals.OutPortal>
  </portals.OutPortal>
</div> 

It seems only the inner portals.OutPortal takes effect whereas the outer doesn't.
Aka:

  • Expect: Children can consume both CounterProvider and ColorProvider
  • Actual: Children can consume only CounterProvider

Here is the codesandbox of the problem

Support for Preact

I have been using this library with React and it has been extremely helpful and I haven't encountered any problems with it. Recently I started working on a project in Preact and I can't seem to get the portals to work properly.

I am getting the following errors:

src/app.tsx:23:11 - error TS2607: JSX element class does not support attributes because it does not have a 'props' property.

23           <portals.OutPortal node={portalNode}></portals.OutPortal>
             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/app.tsx:23:12 - error TS2786: 'portals.OutPortal' cannot be used as a JSX component.
  Its instance type 'OutPortal<unknown>' is not a valid JSX element.
    Type 'OutPortal<unknown>' is missing the following properties from type 'Component<any, any>': state, props, context, setState, forceUpdate

23           <portals.OutPortal node={portalNode}></portals.OutPortal>
              ~~~~~~~~~~~~~~~~~

src/ThruModal.tsx:49:7 - error TS2607: JSX element class does not support attributes because it does not have a 'props' property.

49       <portals.InPortal node={portalNode}>
         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

src/ThruModal.tsx:49:8 - error TS2786: 'portals.InPortal' cannot be used as a JSX component.
  Its instance type 'InPortal' is not a valid JSX element.
    Type 'InPortal' is missing the following properties from type 'Component<any, any>': state, props, context, setState, forceUpdate

49       <portals.InPortal node={portalNode}>
          ~~~~~~~~~~~~~~~~

Is this library compatible with Preact? I should mention I am using Vite / rollup to create my builds where in the past when using React I used react-scripts / web-pack.

Some non-hook function results are not preserved after conditional OutPortal

I would like to

  • transfer components which render results depend on nondeterministic, direct function call(such as Math.random() or Date.now()) and
  • fix the results before and after transfer, without storing them internally
  • OutPortal for the components are conditional

And for these purposes, it seems like this package doesn't do what I expected.


Here's the MWE example: CodeSandbox Link

//Box.tsx
function Box({ txt }: BoxProps) {
  return (
    <div style={{ border: "1px solid red" }}>
      <strong>{txt}</strong>
      <br />
      {Date.now()}
    </div>
  );
}
export const MemoBox = memo(Box);
//App.tsx
import { MemoBox } from "./Box";

function App() {
  const [show, setShow] = useState(true);
  const node1 = useMemo(createHtmlPortalNode, []);
  const node2 = useMemo(createHtmlPortalNode, []);
  const toggle = useCallback(() => { setShow((show) => !show); }, []);

  return (
    <>
      <InPortal node={node1}> <MemoBox txt="111" /> </InPortal>
      <InPortal node={node2}> <MemoBox txt="222" /> </InPortal>
      <button onClick={toggle}>Click me!</button>

      <div>
        {show && <OutPortal node={node1} />}
        {show && <OutPortal node={node2} />}
      </div>
      <div>
        {!show && <OutPortal node={node1} />}
      </div>
    </>
  );
}

Every time I toggle, Box "111" alters its position and Box "222" repeats hiding and showing. Now let's focus on the timestamp.
Box "111" prints with the fixed timestamp, because every render exposes either OutPortal node1. This is fine.
Box "222" however, the timestamp has changed on every show, and this behavior is not quite intuitive when the package description says:

Rendering to zero OutPortals is fine: the node will be rendered as normal, using just the props provided inside the InPortal definition, and will continue to happily exist but just not appear in the DOM anywhere.

rather, it looks like when OutPortal node2 is absent, the detached node2 has been garbage collected and previous timestamp is gone forever.

I'm aware that below solutions will fix Box "222" timestamp as well, so that my purposes are satisfied:

  • modifying the Box implementation (namely using useEffect and useState) to store the timestamp right after mounting
  • or even simpler, insert !show && OutPortal node2 as well but wrapped inside a display:none div.

yet I'm curious why did the timestamp change when the component is supposed to be memoized - given that without these portals React.memo() will do its work and fix the whole results.

moving OutPortal doesn't cause unmount/mount, but *does* cause re-render

is that normal?

    return <div ref = { shellRef } data-type = 'cellshell' data-scrollerid = {scrollerID} data-index = {index} data-instanceid = {instanceID} style = {styles}>
            { ((cellStatus == 'render') || (cellStatus == 'renderplaceholder')) && <OutPortal node = {reverseportal} /> }
            { (cellStatus == 'render1') && <OutPortal node = {reverseportal} /> }
        </div>

Both 'render' and 'render1' cause rendering of InPortal content (the console message is logged each time):

const GenericItem = (props) => {
    const [genericstate, setGenericState] = useState('setup')
    useEffect(()=>{
        if (genericstate == 'setup') {
            setGenericState('final')
        } 
    },[genericstate])
    console.log('rendering generic item index, genericstate',props.index,genericstate)
    return <div style = {{position:'relative',height:'100%', width:'100%',backgroundColor:'white'}}>
        <div style = {genericstyle}>
            {props.index + 1}{false && <img style= {{height:'100%'}} src={props.image}/>}
        </div>
    </div>
}

... but state ('final') is preserved

I was hoping that the move wouldn't trigger a render at all.

Not rendering before an outPortal is used

Hello, is there a way to differ the first render of the InPortal until the first outPortal is rendered? I want to prevent useless render / enable lazy loading for scenarios where the OutPortal would not be used in the user's navigation.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.