Animation Loop to useFrame
Map requestAnimationFrame updates to React Three Fiber's frame hook without turning every tick into React state.
React Three Fiber Three.js
Pinned example stack
react@19.1.1react-dom@19.1.1@react-three/fiber@9.3.0three@0.180.0
Exact Conversion
Switch between the React Three Fiber and Three.js examples, then test the R3F result in the live preview below.
import { useRef } from 'react';import { Canvas, useFrame } from '@react-three/fiber';
function SpinningCube() { const meshRef = useRef(null);
useFrame(() => { if (!meshRef.current) return; meshRef.current.rotation.x += 0.01; meshRef.current.rotation.y += 0.015; });
return ( <mesh ref={meshRef}> <boxGeometry args={[1.5, 1.5, 1.5]} /> <meshStandardMaterial color="#38bdf8" /> </mesh> );}
export default function App() { return ( <div style={{ height: '100vh', background: '#0b1120' }}> <Canvas camera={{ fov: 60, position: [0, 0, 4] }} dpr={[1, 2]}> <color attach="background" args={['#0b1120']} /> <directionalLight position={[2, 3, 4]} intensity={1.4} /> <SpinningCube /> </Canvas> </div> );}import * as THREE from 'three';
const scene = new THREE.Scene();scene.background = new THREE.Color('#0b1120');
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);camera.position.set(0, 0, 4);
const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));renderer.setSize(window.innerWidth, window.innerHeight);document.body.style.margin = '0';document.body.appendChild(renderer.domElement);
const cube = new THREE.Mesh( new THREE.BoxGeometry(1.5, 1.5, 1.5), new THREE.MeshStandardMaterial({ color: '#38bdf8' }));const light = new THREE.DirectionalLight('#ffffff', 1.4);light.position.set(2, 3, 4);
scene.add(light, cube);
function onResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight);}
window.addEventListener('resize', onResize);
function animate() { cube.rotation.x += 0.01; cube.rotation.y += 0.015; renderer.render(scene, camera); requestAnimationFrame(animate);}
animate();Live preview
What Changed
requestAnimationFrame(animate)becomesuseFrame(...).- The mutable object reference moves into
useRef. - Rotation updates still mutate the underlying Three.js object directly.
- React state is not the right tool for per-frame transforms like this.
Why The Ref Matters
Section titled “Why The Ref Matters”In Three.js, you already mutate the mesh each frame. In R3F, that pattern still exists. The React part is how you declare the scene, not a requirement to drive every animation through component state.