const mesh = useRef<THREE.Mesh>(null); // mesh has type RefObject<THREE.Mesh | null>
...
useFrame((): void => {
if (mesh.current != null) {
mesh.current.rotation.x += 0.01;
}
});
...
return <mesh ref={mesh} ... />
As others have mentioned, the ref will typically attach by the time a `useEffect` hook is called, but for safety it's nice to have the null check. const mesh = useRef(...
assigns an instance of something (`React.mutableRefObject`) to a variable in scope, but <mesh ...
after compiling into normal JavaScript and becomes (0, _jsxRuntime.jsxs)("mesh"...
(see `console.log(__SANDBOX_DATA__.data.transpiledModules['/src/App.js:'].source.compiledCode) `)So that's why you're not seeing the editor or runtime complain about this. `<mesh` (lowercase), as with other lowercased react elements get translated into `Something('mesh'`, and doesn't require a defined component variable.
Think this:
const div = 'not a div -- i am a string'
return <div>{div}</div>
Just one of the many gotchas of many layers of JavaScript/TypeScript/React/etc... programming environments.> Is there an error in the examples? You have
const mesh = useRef()
...
<mesh ref={mesh} ...
> You'll be rendering an undefined element (before the ref has a chance to attach).…as follows:
1. On that last line, <mesh> is just a regular element[0].
2. Ref assignment makes this element’s instance accessible within component code[1] under a constant, here coincidentally (and confusingly, I must say) named “mesh”. The constant could’ve been named anything else, like “el”, and the example would work exactly the same way:
const el = useRef()
...
<mesh ref={el} ...
3. How come the constant in the original example does not shadow the equivalently named built-in JSX element? Well, there is a convention/hard-coded rule in React/JSX/TSX where it simply would not resolve lowercase identifiers to custom components. If we change “mesh” to “Mesh” in the original example, then the app would attempt to use the ref constant as a (malformed) custom component, and predictably throw.[0] https://www.w3.org/TR/2016/CR-SVG2-20160915/shapes.html#Mesh...
[1] https://reactjs.org/docs/refs-and-the-dom.html#refs-and-func...
null! is a common typescript thing, it is a semantic guarantee that the ref is static and hence will be available. this saves you the if (ref.current) { ... } check.
const ref = useRef()
useEffect(() => console.log(ref), [])
return <div ref={ref} />
will return { current: [dom node] } const ref = useRef()
useEffect(() => console.log(ref), [])
return <mesh ref={ref} />
will return { current: [mesh node] }I don’t understand your first issue. The component is assigned to the ref, not the other way around, so why would it render `undefined`? It’s possible that `mesh.current` is undefined at first render, but that shouldn’t matter for the component, only for the reference.
const mesh = useRef()
<mesh />
The underlying JavaScript (i.e. not JSX) on the first render , is React.createElement(mesh /* { current: undefined } */, { ref: mesh } )
In fact, I don't even think it matters which render you're talking about. It's still rendering a ref (again, { current: <undefined or a Three.MESH, I guess> }). That doesn't seem right to me.
The reason r3f uses lower-case names for built-in components is to distinguish them from your own components; from the documentation: "It merely expresses Three.js in JSX: <mesh /> becomes new THREE.Mesh(), and that happens dynamically." So whenever you see <mesh /> or anything else lower-case it is just an JSX alias for a Three.js core component. Note that they're not HTML tags in the resulting output; since they're rendered inside <Canvas />, they're transformed to Three.js automatically.
Pretty confusing though, would have been better to call it `meshRef`. I also expected these components to be capitalised, but it probably makes sense in the way that in the dom renderer default components are lowercase as well.
I don't really like this though, although interesting, it feels like a hack and might not integrate well with tooling (e.g. the Typescript example).
How I would have implemented it was exposition a Mesh component which wraps this, and a useMesh hook to wrap and type the ref.
...no, no, definitely not. Must've gone crazy for a second there.
Also, the TypeScript example makes me head hurt.
Ah, yes. A non-null null literal.