I'm using redux to connect child elements in my scene.
This one took a few hours to track down. I have a simple component connected to redux, like:
@connect(state => ({}))
class MyFriend extends Component {
render() {
return null;
}
}
If I render it outside a <Canvas>, it works fine:
<div>
<MyFriend />
<Canvas />
</div>
However if I render it inside a Canvas:
<div>
<Canvas>
<MyFriend />
</Canvas>
</div>
Then the app crashes:
Uncaught Invariant Violation: Could not find "store" in the context of "Connect(MyFriend)"
I suspect it's related to the custom rendering here, but I'm still digging into it: https://github.com/drcmda/react-three-fiber/blob/master/src/canvas.js#L197-L204
I tried returning the first argument here but same result so far https://github.com/drcmda/react-three-fiber/blob/master/src/reconciler.js#L237-L242
This didn't work either:
Canvas.contextTypes = { store: PropTypes.object };
Canvas.childContextTypes = { store: PropTypes.object };
I'm not sure if this is related to the useEffect() call internally, or the .memo() of the Canvas component?
This works (I think), which should probably give me a clue as to what's going on. It's not ideal:
import { ReactReduxContext, Provider } from 'react-redux';
...
<ReactReduxContext.Consumer>
{({ store }) => (
<Canvas>
<Provider store={store}>
...
</Provider>
</Canvas>
)}
</ReactReduxContext.Consumer>
Ah yes, React is not able to do this, sorry. There have been bug issues about this in all reconcilers i know. Here's one from react-konva in which @gaearon says that currently context chokes at the threshold of a renderer in a renderer.
https://github.com/konvajs/react-konva/issues/188
https://github.com/facebook/react/issues/13336
Your solution, forwarding the context, is the only way right now. I very much wish that the React team will fix this.
Btw. Redux 7 is dropping context again (due to various issues), they'll go back to a subscription model. This would allow connect to cross render boundaries. They have a 7 beta out already. (paging @markerikson in case i got this wrong). That was also the reason we pinned redux at version 5 (we use a reconciler at work and once redux went context all blew up).
@drcmda : we're dropping _propagating state changes_ via context, but we're still using new context to pass down the store itself. That may not go through different reconcilers correctly.
I had some hopes but that makes sense, so it looks like this won't be fixed anytime soon. I think forwarding context is the only solution currently.
I made a helper function that forwards a context to work around this issue:
import { useContext, createElement } from 'react';
export const forwardContext = (Context) => (Component) => ({ children, ...props }) => {
const value = useContext(Context);
const wrapped = createElement(Context.Provider, { value }, children);
return createElement(Component, props, wrapped);
};
And then (note that you use WrappedCanvas instead of Canvas:
import { Canvas } from 'react-three-fiber';
import SomeContext from './somewhere';
import { forwardContext } from './helpers';
const WrappedCanvas = forwardContext(SomeContext)(Canvas);
const App = ({ children }) => (
<WrappedCanvas >
{children}
</WrappedCanvas >
);
@drcmda there is better option described here: https://standard.ai/blog/introducing-standard-view-and-react-three-fiber-context-bridge/
I've used it successfully in my react-three-fiber project.
Could be useful to include it to the library and/or docs
@JustFly1984
We rolled out a useContextBridge hook in the @react-three/drei package for official use within the react-three-fiber ecosystem.
function SceneWrapper() {
// bridge any number of contexts
const ContextBridge = useContextBridge(ThemeContext, GreetingContext)
return (
<Canvas>
<ContextBridge>
<Scene />
</ContextBridge>
</Canvas>
)
}
function Scene() {
// we can now consume a context within the Canvas
const theme = React.useContext(ThemeContext)
const greeting = React.useContext(GreetingContext)
return (
//...
)
}
It's been working out really well and the api is pretty friendly :)
Most helpful comment
This didn't work either:
I'm not sure if this is related to the
useEffect()call internally, or the.memo()of the Canvas component?This works (I think), which should probably give me a clue as to what's going on. It's not ideal: