Having a hard time implementing my plain threejs project into a react project without breaking it, specifically problematic with controls

Hi, I’m having a lot of issues implementing my plain threejs project into a react project. Basically, I have a react app with a bunch of pages and I want one of them to be all threejs-y but I’m struggling a lot with adding controls and getting the cooler parts to work. For reference, here’s my plain threejs project:

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

/**
 * Base
 */
// Canvas
const canvas = document.querySelector('canvas.webgl')

// Scene
const scene = new THREE.Scene()

/**
 * Object
 */
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 })
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

/**
 * Sizes
 */
const sizes = {
    width: window.innerWidth,
    height: window.innerHeight
}

window.addEventListener('resize', () =>
{
    // Update sizes
    sizes.width = window.innerWidth
    sizes.height = window.innerHeight

    // Update camera
    camera.aspect = sizes.width / sizes.height
    camera.updateProjectionMatrix()

    // Update renderer
    renderer.setSize(sizes.width, sizes.height)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})

/**
 * Fullscreen
 */
window.addEventListener('dblclick', () =>
{
    const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement

    if(!fullscreenElement)
    {
        if(canvas.requestFullscreen)
        {
            canvas.requestFullscreen()
        }
        else if(canvas.webkitRequestFullscreen)
        {
            canvas.webkitRequestFullscreen()
        }
    }
    else
    {
        if(document.exitFullscreen)
        {
            document.exitFullscreen()
        }
        else if(document.webkitExitFullscreen)
        {
            document.webkitExitFullscreen()
        }
    }
})

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.z = 3
scene.add(camera)

// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

/**
 * Animate
 */
const clock = new THREE.Clock()

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // Update controls
    controls.update()

    // Render
    renderer.render(scene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

tick()

And then here is what I have so far on the react project one:

import React, { Component } from "react";
import { createRoot } from "react-dom/client";
import * as THREE from "three";
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

export default class SimVis extends Component {
  componentDidMount() {

    var canvas = document.querySelector('canvas.webgl')

    var scene = new THREE.Scene()

    var geometry = new THREE.BoxGeometry(1,1,1)
    var material = new THREE.MeshBasicMaterial({ color: 0xff0000})
    var mesh = new THREE.Mesh(geometry, material)
    scene.add(mesh)

    var sizes = {
        width: window.innerWidth,
        height: window.innerHeight
    }

    window.addEventListener('resize', () =>
    {
            // Update sizes
        sizes.width = window.innerWidth
        sizes.height = window.innerHeight

        // Update camera
        camera.aspect = sizes.width / sizes.height
        camera.updateProjectionMatrix()

        // Update renderer
        renderer.setSize(600,800)
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
    })

    window.addEventListener('dblclick', () =>
    {
        var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement

        if(!fullscreenElement)
        {
            if(canvas.requestFullscreen)
            {
                canvas.requestFullscreen()
            }
            else if(canvas.webkitRequestFullscreen)
            {
                canvas.webkitRequestFullscreen()
            }
        }
        else
        {
            if(document.exitFullscreen)
            {
                document.exitFullscreen()
            }
            else if(document.webkitExitFullscreen)
            {
                document.webkitExitFullscreen()
            }
        }
    })
    
    var camera = new THREE.PerspectiveCamera( 75, sizes.width / sizes.height, 0.1, 100);
    camera.position.z = 3;
    scene.add(camera)

    var controls = OrbitControls(camera, canvas)

    var renderer = new THREE.WebGLRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.render( scene, camera );
    // document.body.appendChild( renderer.domElement );
    // use ref as a mount point of the Three.js scene instead of the document.body
    this.mount.appendChild( renderer.domElement );
    // scene.add( mesh );
    // var animate = function () {
    //   requestAnimationFrame( animate );
    // };
    // animate();
    
  }
  render() {
    return (
      <div ref={ref => (this.mount = ref)} />
    )
  }
}

const domNode = document.getElementById('root');
const root = createRoot(domNode);
root.render(<SimVis />);
// const rootElement = document.getElementById("root");
// ReactDOM.render(<SimVis />, rootElement);
// createRoot(<SimVis />, rootElement);

So the main things that are causing problems from what I can gather is that I don’t understand how to use useEffect() instead of componentDidMount() and I have no idea how this.mount.appendChild(…) is working. Please help

it would not make sense to throw three into a useeffect. all you can get out of it is friction since you’re pitting a declarative environment against an imperative one with zero interop and integration. it’s not what react is for.

GitHub - pmndrs/react-three-fiber: 🇨🇭 A React renderer for Three.js is a renderer like react-dom, react-native etc, it’s not a binding or wrapper. you have full integration with context, routes, suspense, state, everything reaches into your canvas.

the code above is just this Basic demo - CodeSandbox

import { Canvas } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'

<Canvas>
  <mesh>
    <boxGeometry />
    <meshBasicMaterial color="#ff0000" />
  </mesh>
  <OrbitControls />
</Canvas>