renderer.render() method makes an WebGL error in useEffect

import React, { useEffect, useRef } from 'react';
import './App.css';
import * as THREE from 'three'

function App() {
  console.log('App');

  const threeCanvas = useRef<HTMLCanvasElement>(null);
  useEffect(() => {
    const scene = new THREE.Scene()
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
    camera.position.set(0, 0, 50)
    const canvas = threeCanvas.current
    if (!canvas) return
    const boxGeometry = new THREE.BoxGeometry(10, 10, 10)
    const boxMaterial = new THREE.MeshNormalMaterial()
    const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)
    const renderer = new THREE.WebGLRenderer({ canvas })
    scene.add(boxMesh)
    const animate = () => {
      boxMesh.rotation.x += 0.01;
      boxMesh.rotation.y += 0.01;
      renderer.render(scene, camera)
      window.requestAnimationFrame(animate)
      // console.log('animate');

    }
    animate()
  })
  return (
    <div className="App">
      <canvas id="threeCanvas" ref={threeCanvas} />
    </div>
  );
}

export default App;

error in console.

three.module.js:17398 WebGL: INVALID_OPERATION: uniformMatrix4fv: location is not from current program
setValueM4 @ three.module.js:17398
setValue @ three.module.js:18026
setProgram @ three.module.js:28321
WebGLRenderer.renderBufferDirect @ three.module.js:27254
renderObject @ three.module.js:27862
renderObjects @ three.module.js:27831
renderScene @ three.module.js:27753
WebGLRenderer.render @ three.module.js:27573
animate @ App.tsx:23
requestAnimationFrame (async)
animate @ App.tsx:24
requestAnimationFrame (async)
animate @ App.tsx:24
(anonymous) @ App.tsx:28
commitHookEffectListMount @ react-dom.development.js:23150
commitPassiveMountOnFiber @ react-dom.development.js:24926
commitPassiveMountEffects_complete @ react-dom.development.js:24891
commitPassiveMountEffects_begin @ react-dom.development.js:24878
commitPassiveMountEffects @ react-dom.development.js:24866
flushPassiveEffectsImpl @ react-dom.development.js:27039
flushPassiveEffects @ react-dom.development.js:26984
(anonymous) @ react-dom.development.js:26769
workLoop @ scheduler.development.js:266
flushWork @ scheduler.development.js:239
performWorkUntilDeadline @ scheduler.development.js:533

This creates a new three-renderer and a new frameloop on every prop change, or when the component gets triggered, you’re pulling the rug. It makes little sense to use three in react like that because you’re mixing two conflicting worlds: oop and fp, loosing all interop. I would suggest you use react-three-fiber: Basic demo - CodeSandbox

1 Like

And if we don’t want to use react-three-fiber, what would be the proposed solution? Also, I don’t think that’s correct since I put a console.log(‘useEffect’) in my useEffect() and it does not log. My stack trace looks a bit different.

setValueM4	@	three.module.js:17409
setValue	@	three.module.js:18165
setProgram	@	three.module.js:28561
WebGLRenderer.renderBufferDirect	@	three.module.js:27632
renderObject	@	three.module.js:28263
renderObjects	@	three.module.js:28232
renderScene	@	three.module.js:28154
WebGLRenderer.render	@	three.module.js:27974
CanvasManager.animate	@	CanvasManager.ts:262
(anonymous)	@	CanvasManager.ts:265
requestAnimationFrame (async)		
CanvasManager.animate	@	CanvasManager.ts:265
(anonymous)	@	CanvasManager.ts:265
requestAnimationFrame (async)		
CanvasManager.animate	@	CanvasManager.ts:265
(anonymous)	@	CanvasManager.ts:265
requestAnimationFrame (async)
...

in that case i wouldn’t use react. if you don’t want <div> but document.createElement(“div”) that’s vanilla js. ask yourself if not wanting react-dom in react makes any sense at all. the same exact thing applies to three. in the end mixing imperative and declarative is what’s causing the issues here, and always will, using react is the only sensible solution, it’s literally why it exists.

as for use effects, you can read about it here. the same applies to suspense or scheduling, react can unmount components at will, if your useEffect has side effects but no clean up phase you risk race conditions and double execution. as for logs, i’m not sure if that’s still true but react would hijack console.log, shutting it off so you didn’t see two logs but the effect ran twice anyway.

I was getting this error and my solution was to cancel the animation frame when my useEffect returns.

“RequestAnimationFrame returns a long integer value, the request id, that uniquely identifies the entry in the callback list. This is a non-zero value, but you may not make any other assumptions about its value. You can pass this value to window.cancelAnimationFrame() to cancel the refresh callback request.”

More info here

So I created a variable

let myReq

Got the request ID

const animate = () => {
      .
      .
      .
      myReq = window.requestAnimationFrame(animate)
    }
animate()

Added a return on my useEffect with the cancelAnimationFrame() function and passed the request ID as param

useEffect(() => {
  .
  .
  .
  return () => { 
    window.cancelAnimationFrame(myReq)
  }
}, [])

It’s ok for small projects but as you implement more features you will face more issues. I’m following your suggestion and am going to use react-three-fiber drcmda.

I stumbled upon this error whilst playing around with Three.js recently. My fix was to save an instance of WebGLRenderer in a useRef object so it is skipped across react’s lifecycle updates.

create a new reference directly to a DOM element react useRef

const rendererRef = useRef<WebGLRenderer>();

instantiate WebGLRenderer and update renderRef’s current value.

  useEffect(() => {
    if (canvas.current) {
      const webGLRenderer = new THREE.WebGLRenderer({
        canvas: canvas.current,
      });

      webGLRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
      webGLRenderer.setClearAlpha(0.1);

      renderer.current = webGLRenderer;
    }
  }, [canvas.current]);

Here is a working codesandbox link