Declarative 'react-babylonjs'

This page demonstrates how to create a truly reactive Babylon.js app. We'll learn how to make custom Babylon React components, and how to use some helpful hooks like useScene, useBeforeRender.

Creating a Basic Scene

Let's take a look at a basic react-babylonjs scene using the Engine and Scene components of react-bablylonjs. These will connect and create an HTML canvas, Engine and Scene with a render loop. Everything you might like to add to a scene can be added declaratively in the Scene. Note we can also pass in props like below with antialias/antialias={true} to the Babylon.js Engine.

import { Engine, Scene } from 'react-babylonjs'
const BabylonApp: FC = ({ children }) => (
<div style={{ flex: 1, display: 'flex' }}>
<Engine antialias adaptToDeviceRatio canvasId="babylon-canvas">
<Scene>{children}</Scene>
</Engine>
</div>
)

If you wanted to re-use some components you could flow down your scene graph as shown below. The React concept composition will be demonstrated further in these guides in a Babylon context.

import { FC } from 'react'
import { Engine, Scene } from 'react-babylonjs'
const BasicScene: FC = ({ children }) => (
<Engine antialias adaptToDeviceRatio canvasId="babylon-canvas">
<Scene>{children}</Scene>
</Engine>
)
const BabylonApp: FC = ({ children }) => (
<div style={{ flex: 1, display: 'flex' }}>
<BasicScene>{children}</BasicScene>
</div>
)

Adding light, camera, and surface

The scene above is empty and won't display anything. To see something, we need to add light, a camera, and something to look at. Let's do that now.

As we go along, it's important to understand a concept known as intrinsic JSX. Renderers treat them as Host Elements as opposed to React Components, much like the react-dom renderer would for HTML elements like div or span. We had to explicitly import Engine and Scene, but there are many Babylon.js elements that will be understood by React thanks to this library. We will use <freeCamera ... />, <hemisphericLight ... />, and <ground ... />. Those are all intrinsic, meaning we don't need an import statement to use them. They are understood by your IDE and include intellisense and compile time warnings. In a Babylon.js application it means you can add your cameras, lights, etc. in that familiar declarative way to your scene:

<Scene>
<freeCamera
name="camera1"
position={new Vector3(0, 5, -10)}
setTarget={[Vector3.Zero()]}
/>
<hemisphericLight
name="light1"
intensity={0.7}
direction={new Vector3(0, 1, 0)}
/>
<ground name="ground" width={6} height={6} />
</Scene>

Click the "code" tab to see the code and you can toggle between TypeScript or JavaScript.

Adding an object to the scene

Before we move on, let's add a standard box to the scene and see how the props work.

If we create a box in Babylon.js imperatively you could call CreateBox:

// doesn't need to be MeshBuilder - there are creation methods that can be imported directly.
import { MeshBuilder } from '@babylonjs/core'
// NOTE: you need to pass in `scene` object yourself.
const box = MeshBuilder.CreateBox('box', { size: 2 }, scene);
box.position = new Vector3(0, 1, 0);
box.rotation = Vector3.Zero();

The equivalent way using react-babylonjs:

<box
name="box"
size={2}
position={new Vector3(0, 1, 0)}
rotation={Vector3.Zero()}
/>

You may be wondering why the element is called "box" - that's because we are using the factory creation method name (without "Create" prefix).

What's important to note is that the constructor arguments name and size are passed in and are used once only. The resulting object that you have props available for is a Mesh object, which has properties like position and rotation. When non-constructor props get new values they are set on the underlying Babylon.js object automatically - the same as you may be accustomed to with CSS or element attributes in DOM. Lastly, notice that we didn't need to pass in a scene object - that is done automatically.

Let's quickly look with FreeCamera at a regular Babylon.js object that's not using a creation method to see how most objects are created.

This is how we'd create a FreeCamera declaratively:

import { FreeCamera, Vector3 } from '@babylonjs/core'
var camera = new FreeCamera('camera1', new Vector3(0, 5, -10), scene)
// This targets the camera to scene origin
camera.setTarget(Vector3.Zero())

This is the equivalent in react-babylonjs:

<freeCamera
name="camera1"
position={new Vector3(0, 5, -10)}
setTarget={[Vector3.Zero()]}
/>

In this case we passed in the name "camera1" and initial position. When calling a method we can use an array for the parameters. The position is also a property, so we can update that prop to change it as well.