brianzinn / react-babylonjs

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

How to get a ref for <Model> component inside <Suspense> #184

Closed br-matt closed 2 years ago

br-matt commented 2 years ago

It seems React 17 does not allow that, throwing the error:

Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Is there some known work around for this ? I need the ref for using the useClick hooks and others on

brianzinn commented 2 years ago

what about something like this - the models are a hierarchy, so will let you choose the ref.

// I haven't tried this, but
const myRef = useRef(null);
useClick((ev) => console.log(ev), myRef);
const onModelLoaded = (loadedModel) => {
  // choose a mesh here:
  myRef.current = ...;
}

return (
  <Model ... />
)

You may have more success with declarative syntax by not using the Model component and attaching directly to meshes with refs. Here is the component: https://github.com/brianzinn/react-babylonjs/blob/master/src/customComponents/Model.tsx

br-matt commented 2 years ago

The first approach suggested seemed to just never register with the hook until a re-render happens and than all sorts of weird bugs. The second approach still requires Suspense it turns out and back to the initial issue. I was able to use forwardRef to get it working though:

const PortalMesh = React.forwardRef(({baseUrl, fileUrl, itemId, index, showVariantMenu, handleVariantClick}, ref) => {

    const sceneLoaderResults = useSceneLoader(
        baseUrl,
        fileUrl,
        null,
        null
    );

    useClick((evt) => {
        handleVariantClick(itemId)
        showVariantMenu(evt.sourceEvent)
    }, ref)

    useEffect(() => {
        return () => {
          sceneLoaderResults.dispose();
        }
      }, []);

    if (sceneLoaderResults.status !== "Loaded") {
         return null;
    }

    return <Mesh
        ref={ref}
        name={'abcd' + index}
        metaData={{test: 'data'}}
        fromInstance={sceneLoaderResults.meshes[1]}
    ></Mesh>
})

export default PortalMesh
<Suspense fallback={null}>
   <PortalMesh ... />
</Suspense>

One side question, how come the metaData prop does not make it to the mesh ? The only metadata it ends up with is the key 'gltf'

brianzinn commented 2 years ago

I need to have the renderer update the mesh - currently that prop is ignored: https://github.com/brianzinn/react-babylonjs/blob/master/src/generatedCode.ts#L207

Hadn't noticed metadata being set to 'gltf' - If it's needed anywhere post-loading then would want to destructure what I am sending through. ie:

mesh.metadata = {...mesh.metadata, ...props.metadata};

I don't have that built-in as a way to update yet. I thought internal usage of metadata by framework was done on reservedDataStore. I suppose this can be safely overwritten - probably it's used by the loader... image

br-matt commented 2 years ago

Yep, that works for now for metadata, thanks !

brianzinn commented 2 years ago

Thanks - I wish there was a cleaner solution. I think adding support for babylon target to https://github.com/pmndrs/gltfjsx would be really, really great.
https://github.com/brianzinn/react-babylonjs/issues/87#issuecomment-781600205