How to use sceneLoader with React?

I can’t find SceneLoader as a ready-made component so I’m assuming we should write our own. I’m trying to do that but I’m not sure how.

class Game extends React.Component {
    render() {
        return (
            <Engine canvasId="sample-canvas">            
                <LoadScene>
                    <arcRotateCamera name="arc" />
                    <HemisphericLight name="light1" intensity={0.7} direction={Vector3.Up()} />
                </LoadScene>
            </Engine>
        );
    }
}

class LoadScene extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            isLoaded: false
        };
    }  

    render() {
        SceneLoader.Load("/assets/", "paradise.babylon", this.props.babylonJSContext.engine, (scene) => { 
            this.setState({isLoaded: true});
        });
        return (
            <>
                {this.state.isLoaded && this.props.children}
            </>
        );
    }
}

I get an error message that “engine” is undefined. I’m not sure how to refer to the engine.

Where do you define the props with the babylonJSContext?

I saw props.babylonJSContext referenced here react-babylonjs/Scene.tsx at master · brianzinn/react-babylonjs · GitHub

but I don’t know how to pass it properly to my sceneLoader. Somehow I need to access the engine that was created in the parent.

The parent has to render the child and set the engine as a property. So the child needs to define that it will expect it in its props

I’ve looked at the code here react-babylonjs - npm

class WithProps extends React.Component 
{
  ...
  render() {
    return (
      <Engine canvasId="sample-canvas">
        <Scene>
          <FreeCamera name="camera1" position={new Vector3(0, 5, -10)} target={Vector3.Zero()} />
          <HemisphericLight name="light1" intensity={this.state.intensity} direction={Vector3.Up()} />
          <Box name="box" size={4} position={new Vector3(0, 1, 0)}>
            <RotateMeshBehavior radians={this.state.clockwiseChecked ? 0.01 : -0.01} axis={Axis.Y} />
          </Box>
        </Scene>
      </Engine>
    )
  }
}

Somehow the engine is being passed to child component “Scene” but I do not see any props being sent.

Pinging @brianzinn who authored the react code :slight_smile:

The Scene component exposes its props here:

and the engine fills this info there:

The reason you don’t see the props being sent, is that they aren’t sent explicitly like by prop-drilling, which some consider an anti-pattern.
The Engine component is a Provider, which drops the engine object onto the context. The context was moved out of experimental and is a way to avoid prop-drilling.

So, to answer your question there are 2 ways.

  1. Use a “consumer” in code. The default export on scene takes care of automatically adding the consumer export default withBabylonJS(Scene). So that is one way (you can see the HoC here: react-babylonjs/Engine.tsx at master · brianzinn/react-babylonjs · GitHub)
  2. You can use a hook. https://github.com/brianzinn/react-babylonjs/blob/master/stories/babylonjs/3-physics/hooks.stories.js#L10 and there is a storybook story for that.

One thing that stands out is that when you don’t use the component that you won’t have:

  1. A canvas rendered
  2. No scene is created for SceneLoader, no canvas attached to scene and render loop not created.

The code to add models is not generated from the API, but hand coded. I think it would not be too much of a stretch to add scene loader as a new component for the library. The component uses it behind the scenes, so to speak (react-babylonjs/ModelLifecycleListener.ts at master · brianzinn/react-babylonjs · GitHub). I think it should work straight up with no changes to load a .babylon model - if your extension of your file doesn’t match then you can override it to use the correct loader. Are you loading an entire scene and want to use your own loading screen?

If you are just loading one model then maybe you can use:

<Engine>
 <Scene>
 <Model rootUrl='/assets/' sceneFileName='paradise.babylon' />
</Scene>
</Engine>

I have added custom properties like scaleToDimension, but you can use rotate, position as well. Let me know if you have any more questions and also always feel free to create an issue with any feature requests.

Thanks for the thorough response @brianzinn

Yes, I’m trying to load a whole scene with a whole bunch of objects in it. So it seems your suggestion is to use the standard <Scene> component then use <Model> to load my whole scene. That’s cool. I can live with that :slight_smile: