Search code examples
reactjstypescriptreact-three-fiber

Using refs in Three JS useFrame hook


Using react-three-fiber often requires using refs to manipulate objects in the scene. For example, we might want to move a mesh's position on every frame render (see code below).

However, refs in typescript are always potentially undefined. So we have to check if our meshRef is current before we manipulate it:

  useFrame((state) => {
    if (meshRef.current) {
      meshRef.current.position.add(new Vector3(1, 1, 1));
    }
  });

But I imagine that we want the code in our useFrame hook to be highly optimized. If we were using regular javascript react, there'd be no need for the if(meshRef.current). So it seems that just by using typescript, we're sacrificing some performance.

Is there any way around this that doesn't involve telling the typescript compiler to ignore the potential undefined value? i.e //@ts-ignore

Full code example:

import React, { useRef } from "react";
import { Canvas, extend, useFrame, useThree } from "@react-three/fiber";
import "./styles.css";
import { Mesh, Vector3 } from "three";

const Scene = (): JSX.Element => {
  const meshRef = useRef<Mesh>();
  useFrame((state) => {
    if (meshRef.current) {
      meshRef.current.position.add(new Vector3(1, 1, 1));
    }
  });

  return (
    <Canvas camera={{ fov: 75, position: [0, 0, 70] }}>
      <mesh ref={meshRef}>
        <boxBufferGeometry args={[10, 10, 1]} />
      </mesh>
    </Canvas>
  );
};

export default Scene;


Solution

  • You can use the ! suffix to force treating an optional property as a required one.

    const ref = useRef<{ someObj: true }>()
    ref.current!.someObj // typescript allows this
    

    However, this isn't type safe. And will definitely crash the first time it's run when the ref is undefined. But if you can be absolutely sure that this code only runs when the ref has a value, then it should work.


    I wouldn't recommend it though. You're inviting a crash where you didn't realize that the value could actually be missing there.

    The second reason I wouldn't recommend it is that this check you are trying to avoid is really really fast.

    Checkout this benchmark where it compares just doing the thing:

    for (const obj of data) {
      result = result + obj.num;
    }
    

    and where you check for the presence of a thing and then do the thing:

    for (const obj of data) {
      if (obj.num) {
        result = result + obj.num;
      }
    }
    

    On my machine, the second one is slower by less than 5%. And that's the worst case scenario of checking 1,000 times per loop. If you check the presence of a value just once per frame, the performance impact will be so miniscule.

    In short, nullish checks like these are not why your webgl application is slow.