How to use react-three-fiber with rescript-react?

for example to create a sphere in react-three-fiber (r3f)

export default function Sphere() {
  return ( 
      <mesh>
        <sphereBufferGeometry attach="geometry" args={[1.5, 64, 64]} />
        <meshPhysicalMaterial
          attach="material"
          clearcoat={1.0}
          clearcoatRoughness={0}
          metalness={0.9}
          roughness={0.1}
          color={'#1af'}
        />
      </mesh>
  );
}

When I attempt to do this in rescript

module Sphere = {
  @react.component
  let make = () => {
    <mesh>
      <sphereBufferGeometry attach="geometry" args={[1.5, 64, 64]} />
      <meshPhysicalMaterial
        attach="material"
        clearcoat={1.0}
        clearcoatRoughness={0}
        metalness={0.9}
        roughness={0.1}
        color={"#1af"}
      />
    </mesh>
  }
}

it throws an error

 We've found a bug for you!
[0]   /Users/user1/Downloads/code/app/src/ShingleViewer.res:10:36-45
[0]   
[0]    8 ┆ let make = () => {
[0]    9 ┆   <mesh>
[0]   10 ┆     <sphereBufferGeometry attach="geometry" args={[1.5, 64, 64]} />
[0]   11 ┆     <meshPhysicalMaterial
[0]   12 ┆       attach="material"
[0]   
   The function applied to this argument has type
This argument cannot be applied with label ~attach

How can I inter-op with the r3f jsx tags ?

Hmmm, it says on the Readme, the call <mesh /> is basically new THREE.Mesh(), so I wonder if you could write bindings to it like so:

module Mesh = {
  type props = {children: React.element}

  @obj
  external makeProps: (~children: React.element, unit) => props = ""

  @new @module("THREE")
  external make: props => React.element = "Mesh"
}

[Playground]

I did not try it with the actual library, it’s just to give you an idea.

2 Likes

I couldn’t get it to work like that

Unhandled Runtime Error
Error: Objects are not valid as a React child (found: object with keys {uuid, name, type, parent, children, up, position, rotation, quaternion, scale, matrix, matrixWorld, matrixAutoUpdate, matrixWorldNeedsUpdate, layers, visible, castShadow, receiveShadow, frustumCulled, renderOrder, animations, userData, geometry, material}). If you meant to render a collection of children, use an array instead.

The way react-three-fiber works is a bit odd. Lowercase JSX desugars as string literals, not identifiers. So at runtime they are actually looking for "mesh" as opposed to THREE.Mesh to know what to create. In order to write the bindings you’d need to do something like:

module Mesh = {
  type props = {children: React.element}

  @obj
  external makeProps: (~children: React.element, unit) => props = ""

  let make: React.component<props> = Obj.magic("mesh")
}
4 Likes

that worked! thanks a bunch

I didn’t know about Obj.magic. Is that in the docs?

It is in the OCaml manual: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Obj.html#VALmagic

It’s a somewhat advanced technique and not really recommended in daily practice. Since this binding is quite weird, it’s justified here.

2 Likes

Yeah Obj.magic was just an example. Use this instead (and again, only if you need to): https://rescript-lang.org/docs/manual/latest/type#type-escape-hatch

If you’ve got a fuller working example, please post one! We’ll edit the example to avoid folks from copy pasting Obj.magic everywhere

2 Likes

just to explain what’s going on. this is the fundamental way react works, react-reconciler expects a string type in the createInstance hook which then hands down the created instance. <div/>, <mesh/>, it’s all the same principle. r3f is a renderer, like react-dom.

function createInstance(
  type: string, 
  props: any, 
  container: any, 
  hostContext: any, 
  internalInstanceHandle: Reconciler.Fiber
)

in r3f’s case, it receives “mesh” and makes that a return new THREE[uppercase(type)]. similar to react-dom, which receives “div” and returns createElement(type). that instance is then received by the reconciler.

1 Like

@chenglou
I’m trying the following

module Mesh = {
  type props = {
    children: React.element,
    position: option<array<float>>,
    rotation: option<array<float>>,
  }

  @obj
  external makeProps: (
    ~children: React.element,
    ~position: array<float>=?,
    ~rotation: array<float>=?,
    unit,
  ) => props = ""
 
  external make: React.component<props> = "%identity"
}

And it errors

%identity expect its type to be of form 'a -> 'b (arity 1)

Any idea what is wrong here?

Ah %identity externals need to be a function call. Sorry maybe it won’t work here? Tbh I’ve only loosely read the thread and suggested a general tip regarding avoiding Obj.magic :sweat_smile:

1 Like

ahh ok, I was very puzzled :smiley: