[Help] Animation restart every new render...

Hello everybody :grinning:

I’m confronted to a annoying issue with my Vite / React project and Three.js Fiber.

Here’s the problem :

I have 2 files, one is called App.jsx and the another is Scene.jsx.
When the App.jsx rendering is updated (with new angles of the DeviceOrientation), the animation of my cube stored in my Scene.jsx reset…

Same issue happen with all kinds of new rendering (if I use addEventListener for a “click” per example, so the issue don’t come specifically by the ‘deviceorientation’ event).

Here a gif to illustrate the problem : Screen capture - f24bf397bd9eee80510018dc09495af0 - Gyazo
Here my App.jsx and Scene.jsx codes :

App.jsx :

import { useEffect, useRef, useState } from 'react'
import { Scene } from './components/Scene'

const App = () => {
  const [angle, setAngle] = useState(0)
  const [leftToRight, setLeftToRight] = useState(0)
  const [frontToBack, setFrontToBack] = useState(0)
  const eventListenerRef = useRef(null)

  useEffect(()=>{
    if (window.DeviceOrientationEvent && !eventListenerRef.current){
      eventListenerRef.current = e => {
        setAngle(e.alpha)
        setLeftToRight(e.beta)
        setFrontToBack(e.gamma)
      }
      window.addEventListener('deviceorientation', eventListenerRef.current)
    }
    return () => {
      if (eventListenerRef.current){
        window.removeEventListener('deviceorientation', eventListenerRef.current)
        eventListenerRef.current = null
      }
    }
  },[])

  return (
    <>
      <div className='angles-infos'>
        <h3>angle : {angle}</h3>
        <h3>leftToRight : {leftToRight}</h3>
        <h3>frontToBack : {frontToBack}</h3>
      </div>
      <Scene/>
    </>
  )
}

export default App

Scene.jsx :

import { Canvas, useFrame } from '@react-three/fiber'
import { useRef } from 'react'

export const Scene = () => {
    const ref = useRef()

    const Cube = ({position, size, color}) => {
        useFrame((state, delta)=>{
            ref.current.rotation.x += delta
            ref.current.rotation.y += delta
            ref.current.position.z = Math.sin(state.clock.elapsedTime) * 2
        })
        return (
            <mesh position={position} ref={ref}>
                <meshStandardMaterial color={color}/>
                <boxGeometry args={size}/>
            </mesh>
        )
    }

    return (
        <>
        <Canvas>
            <directionalLight position={[0,0,2]}/>
            <Cube position={[0, 0, 0]} size={[1, 1, 1]} color={"orange"}></Cube>
        </Canvas>
        </>
    )
}

Your help would be very appreciated, thanks by advance :slightly_smiling_face:

This may not be the best explanation as React can be weird sometimes and do whatever it feels like but a couple of things. I doubt its the useRef() as stated in their docs, setting this does not trigger a re-render. BUT! on the other hand useState() does as it’s meant to be used in rendering. Plus, you’re using the leftToRight & the frontToBack state in the App.jsx component which is the parent to the Scene.tsx. By having it re-render the 2 states, it could be triggering a reset on the page causing Three.JS child component to rerender the entire scene (essentially the same as reloading the page). Additionally, you said it wasn’t just the device orientation causing it which leads me to believe that its the useState causing it in your useRef.current event. This may be wrong but I would see what happens when you remove the setState functions from the .current function and see if it triggers a re-render.

EDIT: Here is the useRef doc page (link)

Hello, thanks a lot for your answer, I’ve just declared my Cube outside my export const Scene () => and I don’t have this issue anymore :slight_smile:

for later, this is not permitted, you can’t create components in components. i mean, you can, but they don’t have lifecycles, they unmount/remount every render. when react mounts a component it stores a reference to it, but yours isn’t stable, Cube !== previousCube next render because you re-create the function, so in reacts eyes the old cube must unmount, and a new cube must mount. this is what you experience as restart, animations and otherwise.

a stable reference would be this:

const Scene = () => { ... }
const Cube = () => { ... }

Glad to hear