Skip to content

TypeScript

Fresh

Typing with useRef

React's useRef won't automatically infer types despite pointing it to a typed ref. Pass a type through useRef's generics:

tsx
import { useRef, useEffect } from 'react'
import { Mesh } from 'three'

function Box(props) {
  const meshRef = useRef<Mesh>(null!)

  useEffect(() => {
    console.log(Boolean(meshRef.current))
  }, [])

  return (
    <mesh {...props} ref={meshRef}>
      <boxGeometry />
      <meshBasicMaterial />
    </mesh>
  )
}

The exclamation mark (!) is a non-null assertion that lets TypeScript know ref.current is defined when we access it in effects, useFrame et al. You do not need to check against null.

Accessing Typed Three Elements

Use the ThreeElements interface to spread props or type components that rely on three elements:

tsx
import { ThreeElements } from '@react-three/fiber'

type FooProps = ThreeElements['mesh'] & { bar: boolean }

function Foo({ bar, ...props}: FooProps) {
  useEffect(() => {
    console.log(bar)
  }, [bar])
  return <mesh {...props} />
}

Extend Usage

tsx
import { useRef, useEffect } from 'react'
import { GridHelper } from 'three'
import { extend } from '@react-three/fiber'

// Create our custom element
class CustomElement extends GridHelper {}

// Extend so the reconciler will learn about it
extend({ CustomElement })

<customElement />

TypeScript won't know about custom elements yet:

// error: 'customElement' does not exist on type 'JSX.IntrinsicElements'
<customElement />

Extending ThreeElements

Use the ThreeElement interface to extend ThreeElements:

tsx
import { useRef, useEffect } from 'react'
import { GridHelper } from 'three'
import { extend, ThreeElement } from '@react-three/fiber'

// Create our custom element
class CustomElement extends GridHelper {}

// Extend so the reconciler will learn about it
extend({ CustomElement })

// Add types to ThreeElements so primitives pick up on it
declare module '@react-three/fiber' {
  interface ThreeElements {
    customElement: ThreeElement<typeof CustomElement>
  }
}

// Now TypeScript understands it
<customElement />

Factory Signature (shorter)

Use the extend factory signature to automatically extend the element with type inference:

tsx
// Create our custom element
class CustomElement extends GridHelper {}

// Extend so the reconciler will learn about it — types will be inferred
const Element = extend(CustomElement)

// react-three-fiber will create your custom component and TypeScript will understand it
<Element />

Extending Default Three Elements

If you open your own root instead of using <Canvas>:

tsx
import * as THREE from 'three'
import { extend, createRoot, events } from '@react-three/fiber'

// Register the THREE namespace as native JSX elements
extend(THREE as any)

// Create a react root
const root = createRoot(document.querySelector('canvas'))

Exported Types

React Three Fiber exports types for its internals:

tsx
// Event raycaster intersection
Intersection

// useFrame internal subscription and render callback
Subscription
RenderCallback

// useThree's returned internal state
RootState
Performance
Dpr
Size
Viewport
Camera

// Canvas props
CanvasProps

// Supported events
Events

// Event manager signature (completely modular)
EventManager

// Wraps a platform event as it's passed through the event manager
ThreeEvent

v9 TypeScript Changes

Props Renamed to CanvasProps

diff
-function Canvas(props: Props)
+function Canvas(props: CanvasProps)

Dynamic JSX Types

Hardcoded exports like MeshProps have been removed and can be accessed via ThreeElements:

diff
-import { MeshProps } from '@react-three/fiber'
-type Props = MeshProps

+import { ThreeElements } from '@react-three/fiber'
+type Props = ThreeElements['mesh']

Node Helpers Removed

Specialized Node type helpers are removed and combined into ThreeElement:

tsx
import { type ThreeElement } from '@react-three/fiber'

declare module '@react-three/fiber' {
  interface ThreeElements {
    customElement: ThreeElement<typeof CustomElement>
  }
}

extend({ CustomElement })

ThreeElements (Correct Way to Declare JSX)

ThreeElements is the current way of declaring or accessing JSX within R3F:

diff
-import { type Node } from '@react-three/fiber'
-
-declare global {
-  namespace JSX {
-    interface IntrinsicElements {
-      customElement: Node<CustomElement, typeof CustomElement>
-    }
-  }
-}
-
-extend({ CustomElement })

+import { type ThreeElement } from '@react-three/fiber'
+
+declare module '@react-three/fiber' {
+  interface ThreeElements {
+    customElement: ThreeElement<typeof CustomElement>
+  }
+}
+
+extend({ CustomElement })