react-three-fiber icon indicating copy to clipboard operation
react-three-fiber copied to clipboard

useRef returns T or undefined, but NodeProps.ref only returns T

Open arswaw opened this issue 1 year ago • 4 comments
trafficstars

Discussed in https://github.com/pmndrs/react-three-fiber/discussions/3121

Originally posted by arswaw December 14, 2023 Hello. I am using R3F for the first time and I am also using TypeScript. All my component files have the .tsx extension.

I have run into an issue with useRef and this may affect other attributes.

Consider the following code:

<mesh ref={cubeRef} rotation-y={Math.PI * 0.25} position-x={2} scale={1.5}>
   <boxGeometry />
  <meshBasicMaterial color="mediumpurple" />
</mesh>

If I define useRef in the same component like:

const cubeRef = useRef()

Then ref in the <mesh ... /> will return the following error:

Type 'MutableRefObject<undefined>' is not assignable to type 'Ref<Mesh<BufferGeometry<NormalBufferAttributes>, Material | Material[], Object3DEventMap>> | undefined'.
  Type 'MutableRefObject<undefined>' is not assignable to type 'RefObject<Mesh<BufferGeometry<NormalBufferAttributes>, Material | Material[], Object3DEventMap>>'.
    Types of property 'current' are incompatible.
      Type 'undefined' is not assignable to type 'Mesh<BufferGeometry<NormalBufferAttributes>, Material | Material[], Object3DEventMap> | null'.ts(2322)
three-types.d.ts(34, 5): The expected type comes from property 'ref' which is declared here on type 'MeshProps'
(property) ref?: React.Ref<Mesh<BufferGeometry<NormalBufferAttributes>, Material | Material[], Object3DEventMap>> | undefined

The problem is that React useRef is typed like:

function useRef<T = undefined>(): MutableRefObject<T | undefined>;

Whereas in r3f/node_modules/@react-three/fiber/dist/declarations/src/three-types.d.ts we see:

export interface NodeProps<T, P> {
    attach?: AttachType;
    /** Constructor arguments */
    args?: Args<P>;
    children?: React.ReactNode;
    ref?: React.Ref<T>;
    key?: React.Key;
    onUpdate?: (self: T) => void;
}

If ref (and perhaps related attributes) were typed:

ref?: React.Ref<T | undefined>

then it would solve the problem.

When I modified the R3F dependency directly, the error disappeared.

Since that is bad practice, I have used the temporary solution:

const cubeRef = useRef() as MutableRefObject<Mesh>

However this decreases type safety since the ref could return undefined at runtime.

I am not sure if I am in misinterpreting the problem or the solution. Does my new union type make sense?

arswaw avatar Dec 20 '23 00:12 arswaw

I am experiencing the same issue with "@react-three/fiber": "^8.15.19".

ksoldau avatar Mar 26 '24 18:03 ksoldau

I think the type we use in R3F is wrong and should allow undefined. We can align with the DOM types on this.

CodyJasonBennett avatar Mar 26 '24 21:03 CodyJasonBennett

I think this might be the correct behavior, you'll get the same error with a regular DOM element. You need to type your ref like this and init with null. (React will set ref.current = null when the component unmounts)

export const Component = () => {
	const ref = useRef<Mesh>(null);

	return <mesh ref={ref} />;
};

Respectively, this is how you would type a ref to a div

export const Component = () => {
	const ref = useRef<HTMLDivElement>(null);

	return <div ref={ref} />;
};

filahf avatar Apr 16 '24 08:04 filahf

This will be fixed with React 19. Looks like this was an upstream issue.

CodyJasonBennett avatar Apr 28 '24 03:04 CodyJasonBennett