brianzinn / react-babylonjs

React for Babylon 3D engine
https://brianzinn.github.io/react-babylonjs/
809 stars 102 forks source link

createPortal missing container.rootInstance #320

Closed dennemark closed 1 month ago

dennemark commented 1 month ago

Hi, long time I had to write here. I guess that is a good sign :D

I stumbled over an issue - maybe because of my tunneling component. If I use a createPortal in acomponent the following error is thrown:

Uncaught TypeError: e.rootInstance is undefined
    insertInContainerBefore ReactBabylonJSHostConfig.ts:356
    insertOrAppendPlacementNodeIntoContainer react-reconciler.development.js:15577

Now in ReactBabylonJSHostConfig it says, that insertInContainerBefore works similar to appendChildContainer But the latter is actually checking, if rootInstance exists on container for the case of createPortal, while the first is not doing this. @brianzinn do you maybe remember, why this is the case and if it should be possible to do this in insertInContainerBefore?

https://github.com/brianzinn/react-babylonjs/blob/6d16c4eca2030d4b7a17834cded1ad207f6bb87d/packages/react-babylonjs/src/ReactBabylonJSHostConfig.ts#L349

https://github.com/brianzinn/react-babylonjs/blob/6d16c4eca2030d4b7a17834cded1ad207f6bb87d/packages/react-babylonjs/src/ReactBabylonJSHostConfig.ts#L766

it helps to add the same logic to insertInContainerBefore

  insertInContainerBefore(
    container: Container,
    child: CreatedInstance<any>,
    beforeChild: CreatedInstance<any>
  ) {
    // Similar to appendChildToContainer, but indexed insertion
    if (child) {
      if (container.rootInstance) {
...
    } else {
      console.log('addend child with no root (createPortal only?)')
      addChild(container as unknown as CreatedInstance<any>, child)
    }
    } else {
      console.error('insertInContainerBefore. No child:', child)
    }
  },

but then the same issue occurs on removeChildFromContainer So the main issue seems, that the portal node is somehow missing the container right?

brianzinn commented 1 month ago

Yeah, your tunnel component is pretty cool. Sounds like you have a repro working - we can maybe solve it with supporting more methods in the renderer, but I would more suspect you need to pass some context to the tunnel itself? Probably would help with a fork/repro to refresh what's happening. With those dynamic added I can see the reconciler not knowing about the container to flow through (if I am thinking about it correctly)...

dennemark commented 1 month ago

Going to try to make a reproduction.

Solved it right now by using another tunnel 😁

brianzinn commented 1 month ago

can you share the solution - you mean like a tunnel through a tunnel? this reminds me of ssh tunnels through vpn tunnel...

dennemark commented 1 month ago

Hi, I could not make a reproduction yet of the Portal, but the following is roughly the example. (I provided also code with a context-bridge, so all context hooks work within the tunneled components).

The reason I am doing this, is to bring all components to a specific transform node "tunnel-transform-node". For this simple example it is a bit too much, maybe. But in my case the transform node has some important transforms that are updated every now and then.

import {
  PropsWithChildren,
  Ref,
  forwardRef,
  useImperativeHandle,
  useLayoutEffect,
  useRef,
} from 'react'

import { TransformNode } from '@babylonjs/core/Meshes/transformNode
import tunnel from 'tunnel-rat'
import { ContextBridge, FiberProvider, useContextBridge } from 'its-fine'
import { create } from 'zustand'

const NodeTunnel = tunnel('transformNode')

export const NodeTunnel = forwardRef(({ children }: PropsWithChildren, forwardedRef) => {
  return (
    <FiberProvider>
      <BridgedNodeTunnel>
          {children}
        </BridgedNodeTunnel>
    </FiberProvider>
  )
})

const BridgedNodeTunnel = ({ children }: PropsWithChildren) => {
  const Bridge: ContextBridge = useContextBridge()
  return (
    <NodeTunnell.In>
      <Bridge>{children}</Bridge>
    </NodeTunnel.In>
  )
}

export const TunnelToNode = () => {
  return (
    <AnotherTunnel.In>
      <transformNode name="tunnel-transform-node">
        <NodeTunnel.Out />
       </transformNode
    </AnotherTunnel.In>
  )
}
brianzinn commented 1 month ago

Thanks @dennemark for sharing and contributing - interesting to see how you are bridging. Closing as looks resolved. Cheers.