Using a useFrame render hook in react-three-fiber

Drawing a cube, or an aeroplane, is cool, and moving the camera around it is also cool, but a 3D scene is even cooler when things move.

In 3D applications the process of updating the scene to change the position, rotation and attributes of the models and shaders being used is called a "render loop". In essence this is little more than a highly optimized while loop - while the app is running keep updating everything and rendering the output on the screen.

In browsers the most efficient way to update what's on the page is the requestAnimationFrame feature. That's what react-three-fiber uses internally in its useFrame hook. requestAnimationFrame registers a callback function to be called on the next tick of the browser's internal clock. That should be approximately every 16ms.

IMPORTANT NOTE: The structure of the project is changing significantly here. The useFrame hook does a lot more than just provide a requestAnimationFrame interface. It also gives us a React context for the camera, a clock, and so on. That means that useFrame can't be used in the same component that defines the Canvas though, because the Canvas component is also the context provider for what useFrame consumes, so useFrame will only work in a component that's inside of a Canvas.

useFrame is a React hook. If you're not familiar with hooks already it's probably worthwhile going away and reading up on the React website.

When we use a useFrame hook we need to give it a callback function. This is something that updates the things in the scene. However, to reference what's in the scene we need to give things refs. Fortunately React makes that really simple with another hook, useRef.

useRef has a value of null (or whatever you give it, but it's null by default) until it's been populated with something in the scene. Then the .current property can be used to reference what the ref is pointing to. This is important because the fact it's a hook means it hangs around between renders - if we used a variable it'd be lost when the component function runs. Putting these parts together gives us;

const Torus = () => {
  const torusRef = useRef();

  useFrame(() => {
    torusRef.current.rotation.x += 0.03;
    torusRef.current.rotation.y += 0.02;
  });

  return (
    <mesh ref={torusRef}>
      <torusGeometry args={[1, 0.2, 12, 36]} />
      <meshStandardMaterial color={"red"} />
    </mesh>
  );
};

torusRef is a reference to the torus mesh that's returned by the component. Every time the render loop runs (60 times a second) the rotation in the x and y axises is incremented a little. Actually using the spinning torus is as simple as adding <Torus /> to the App.

const App = () => {
  return (
    <Canvas style={{ height: 400, width: 800 }}>
      <pointLight position={[5, 5, 5]} />
      <Torus />
      <OrbitControls />
      <Stats />
    </Canvas>
  );
};

Now the application actually does something. Finally.

This looks pretty good, especially if you're making a website for a hula-hooping business.