You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Currently createPortal only works within the current renderer.
This means that if you want to embed one renderer into another (e.g. react-art in react-dom), your only option is to do an imperative render in a commit-time hook like componentDidMount or componentDidUpdate of the outer renderer's component. In fact that's exactly how react-art works today.
With this approach, nested renderers like react-art can't read the context of the outer renderers (#12796). Similarly, we can't time-slice updates in inner renderers because we only update the inner container at the host renderer's commit time.
At the time we originally discussed portals we wanted to make them work across renderers. So that you could do something like
Thanks for working on this! This was a big pain point for us so we made a workaround. In case this is useful, here's an example of the Context barrier (for react-three-fiber) and how we are fixing it temporarily until there's a fix upstream:
// 👎 Context cannot go through <Canvas>, so <Square> cannot read the rotationReactDOM.render(<TickProvider><Canvas><Square/></Canvas><Consumer>{value=>value.toFixed(2)}</Consumer></TickProvider>,document.getElementById('outside'));// 👎 Context is all inside <Canvas> so it cannot be passed/read from outsideReactDOM.render(<><Canvas><TickProvider><Square/></TickProvider></Canvas>
No access to `rotation` here
</>,document.getElementById('inside'));// 👍 Passes the Context from above, bridging React and react-three-fiber Context// 👎 But this re-renders <Canvas> a lot, partially defeating the point of Context// 👍 memo() Canvas and Square well enough and there's no problem here!ReactDOM.render(<TickProvider><Consumer>{value=>(<Canvas><Providervalue={value}><Square/></Provider></Canvas>)}</Consumer><Consumer>{value=>value.toFixed(2)}</Consumer></TickProvider>,document.getElementById('bridge'));
❤️❤️❤️
Reacted by Anton Lavrenov, binaryta, —, Muhammed Sarı, Hugo Raynal, Igor Moiseev, okathira, monkeytao, celine, Amit Rajput and 10 more
And just to bring it up once, this also applies to error boundaries and suspense. It would be really helpful if a reconciler could read out contextual information (context, errors, suspense) from its parent reconciler and apply it to itself - i think it shouldn't behave much different than a generic portal.
I made something similar to this through a simple implementation that can be shared between renderers.
importReactfrom"react";importReactDOMfrom"react-dom";import{Portal,createBridge}from"@devsisters/react-pixi";import{CountContext}from"./countContext";const$uiRoot=document.getElementById("ui-root");constuiRoot=ReactDOM.createRoot($uiRoot);constuiBridge=createBridge(uiRoot,{sharedContext: [CountContext],});constUIPortal=({ children })=>{return<Portalbridge={uiBridge}>{children}</Portal>;};exportdefaultUIPortal;
I wanted to follow up to say that the solution I proposed above has been working very well for us for 1+ year. The main thing (as I edited in a comment there) is that you have to memo() both the <Canvas> and the <Square> very well, but then everything works very smooth!
We rolled out a useContextBridge hook in the @react-three/drei package for official use within the react-three-fiber ecosystem.
functionSceneWrapper(){// bridge any number of contextsconstContextBridge=useContextBridge(ThemeContext,GreetingContext)return(<Canvas><ContextBridge><Scene/></ContextBridge></Canvas>)}functionScene(){// we can now consume a context within the Canvasconsttheme=React.useContext(ThemeContext)constgreeting=React.useContext(GreetingContext)return(//...)}
It's been working out really well and the api is pretty friendly :)
Reacted by Jin Yao, Michael サイトー 中村 Bashurov, marco gobbi, James Robb and Alistair Smith
Components within TunnelEntrance will be send to TunnelExit. They will use the renderer of TunnelExit and therefore can consume its contexts.
A tunnel allows to render components of one renderer inside another.
I.e. babylonjs components normally need to live within Engine component.
A tunnel entrance allows to position components in a different renderer, such as ReactDOM
and move it to the tunnel exit, that must exist within Engine component.
The nice thing is, even refs can be used outside of Engine context.
The createTunnel function creates a tunnel entrance and exit component.
The tunnel works one-directional.
TunnelEntrance only accepts components that are allowed to live within the renderer of TunnelExit.
Multiple entrances and exits are possible.
If components need to be rendererd the other way around, a second Tunnel is needed.
Edit: There seem to be some race-conditions leading to unwanted side-effects. I.e. if tunnel entrance is used earlier than tunnel exit, components within entrance will not be able to consume context properly. This means, that the components are partly processed already before being supplied to the exit node.
I would like to get back to the first idea of @gaearon . Currently we have solutions for portals within one renderer and context bridges to pass data to a child renderer. However, the idea of the above approach would allow bi-directional communication of renderers, maybe even while using them in parallel.
I am currently facing a situation, where my 2d renderer needs to communicate with a 3d renderer. I could use the react tree approach and change states within a branch of the 2d renderer, throwing it into a context that is bridged down to the 3d renderer and update states over there. ( I cannot do it the other way around with bridges ) In this case I have to deal with data in three different placces. The 2d component, the context and the 3d component.
If we would have a portal like above, we could just keep the logic within a single component.
My tunneling approach above comes close to it. I define a startpoint within one renderer and an endpoint in the other. It is one-directional, but with another tunnel, it is easy to share data in two directions, if necessary. But it is pretty useful to use 2d and 3d renderer logic within one component.
The use of zustand might not be preferable and it currently can lead to side effects in rare cases, that I do not fully understand yet. But I would hope, that there is a bit new research on this topic. Unfortunately I studied archtitecture and not software architecture, so there is a limit to my input :P
if any of this could still land officially in react we would be relieved to say the least. not being able to access context across renderers is a major annoyance for our users, and even though forwarding context was always possible, there is a ton of libraries out there not willing to expose context.
Reacted by Chris Cordle, Marco Gomez, Jeremy Tenjo, pugson, Carl-Erik Kopseng, Gerhard Wesp, Davide Cristini, Hernan J. González, Verekia, Wilson Hou and 2 more
Activity
franciscop-sc commentedon Jul 19, 2019
Thanks for working on this! This was a big pain point for us so we made a workaround. In case this is useful, here's an example of the Context barrier (for
react-three-fiber) and how we are fixing it temporarily until there's a fix upstream:CodeSandbox bridge example
❤️❤️❤️
drcmda commentedon Nov 6, 2019
And just to bring it up once, this also applies to error boundaries and suspense. It would be really helpful if a reconciler could read out contextual information (context, errors, suspense) from its parent reconciler and apply it to itself - i think it shouldn't behave much different than a generic portal.
mabdullahsari commentedon Jan 30, 2020
Well, I hope this gains traction at some point as it's a huge pain point for me right now.
anion155 commentedon Apr 3, 2020
Hi! Any plans on making it come true?
cometkim commentedon Apr 15, 2020
I made something similar to this through a simple implementation that can be shared between renderers.
Demo: https://codesandbox.io/s/react-pixi-portal-lsgh1
User can specify the context object to share, and Portal uses React internal readContext() to pass these contexts to another renderer.
franciscop-sc commentedon Sep 1, 2020
I wanted to follow up to say that the solution I proposed above has been working very well for us for 1+ year. The main thing (as I edited in a comment there) is that you have to
memo()both the<Canvas>and the<Square>very well, but then everything works very smooth!stephencorwin commentedon Nov 12, 2020
We rolled out a
useContextBridgehook in the @react-three/drei package for official use within the react-three-fiber ecosystem.It's been working out really well and the api is pretty friendly :)
dennemark commentedon Oct 27, 2021
Hi,
I was facing similar issues for react-babylonjs. I made a PR for an approach that seems to work, but might still need some testing.
brianzinn/react-babylonjs#168
Interface is rather simple:
Components within TunnelEntrance will be send to TunnelExit. They will use the renderer of TunnelExit and therefore can consume its contexts.
Edit: There seem to be some race-conditions leading to unwanted side-effects. I.e. if tunnel entrance is used earlier than tunnel exit, components within entrance will not be able to consume context properly. This means, that the components are partly processed already before being supplied to the exit node.
dennemark commentedon Nov 30, 2021
I would like to get back to the first idea of @gaearon . Currently we have solutions for portals within one renderer and context bridges to pass data to a child renderer. However, the idea of the above approach would allow bi-directional communication of renderers, maybe even while using them in parallel.
I am currently facing a situation, where my 2d renderer needs to communicate with a 3d renderer. I could use the react tree approach and change states within a branch of the 2d renderer, throwing it into a context that is bridged down to the 3d renderer and update states over there. ( I cannot do it the other way around with bridges ) In this case I have to deal with data in three different placces. The 2d component, the context and the 3d component.
If we would have a portal like above, we could just keep the logic within a single component.
My tunneling approach above comes close to it. I define a startpoint within one renderer and an endpoint in the other. It is one-directional, but with another tunnel, it is easy to share data in two directions, if necessary. But it is pretty useful to use 2d and 3d renderer logic within one component.
The use of zustand might not be preferable and it currently can lead to side effects in rare cases, that I do not fully understand yet. But I would hope, that there is a bit new research on this topic. Unfortunately I studied archtitecture and not software architecture, so there is a limit to my input :P
drcmda commentedon Sep 22, 2022
we added our fix to react-three-fiber today https://twitter.com/0xca0a/status/1573064826339094528
it's using https://github.com/pmndrs/its-fine#fiberprovider (this would work in any react-renderer)
if any of this could still land officially in react we would be relieved to say the least. not being able to access context across renderers is a major annoyance for our users, and even though forwarding context was always possible, there is a ton of libraries out there not willing to expose context.