Skip to content

v9 Migration Guide

Fresh

This is a compatibility release for React 19, which brings further performance, stability, and type improvements.

Breaking Changes

This release contains breaking changes when using Strict Mode, which can highlight bugs during development. See the StrictMode section below.

Features

useLoader Accepts Loader Instance

useLoader now supports re-use of external loader instances for more controlled pooling and setup:

jsx
import { GLTFLoader } from 'three/addons'
import { useLoader } from '@react-three/fiber'

function Model() {
  const gltf = useLoader(GLTFLoader, '/path/to/model.glb')
  // ...
}

// or with a pre-configured loader instance

const loader = new GLTFLoader()
function Model() {
  const gltf = useLoader(loader, '/path/to/model.glb')
  // ...
}

Factory extend Signature

extend can now produce a component when a three.js class is passed individually instead of a catalog of named classes. This is backwards compatible and reduces TypeScript boilerplate:

tsx
import { OrbitControls } from 'three/addons'
import { type ThreeElement, type ThreeElements } from '@react-three/fiber'

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

extend({ OrbitControls })

<orbitControls args={[camera, gl.domElement]}>

// or,

const Controls = extend(OrbitControls)
<Controls args={[camera, gl.domElement]} />

Async GL Prop

The Canvas GL prop callback now passes constructor parameters instead of just a canvas reference:

diff
<Canvas
  gl={{ reverseDepthBuffer: true }}
- gl={(canvas) => new WebGLRenderer({ canvas })}
+ gl={(props) => new WebGLRenderer(props)}
>

A callback passed to GL can now return a promise for async constructors like WebGPURenderer:

tsx
<Canvas
  gl={async (props) => {
    // ...
    return renderer
  }}
>

WebGPU Support

tsx
import * as THREE from 'three/webgpu'
import * as TSL from 'three/tsl'
import { Canvas, extend, useFrame, useThree } from '@react-three/fiber'

declare module '@react-three/fiber' {
  interface ThreeElements extends ThreeToJSXElements<typeof THREE> {}
}

extend(THREE as any)

export default () => (
  <Canvas
    gl={async (props) => {
      const renderer = new THREE.WebGPURenderer(props as any)
      await renderer.init()
      return renderer
    }}>
      <mesh>
        <meshBasicNodeMaterial />
        <boxGeometry />
      </mesh>
  </Canvas>
)

Fixes

Color Management of Textures

Automatic sRGB conversion of texture props has been removed. Color textures are now handled automatically for built-in materials, aligning with vanilla three.js behavior. For custom materials or shaders, annotate color textures:

jsx
texture.colorSpace = THREE.SRGBColorSpace
// or in JSX:
texture-colorSpace={THREE.SRGBColorSpace}

Suspense and Side-Effects

Side-effects like attach and constructor effects no longer fire repeatedly without proper cleanup during suspension:

jsx
import { ThreeElement, useThree } from '@react-three/fiber'
import { OrbitControls } from 'three/addons'

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

extend({ OrbitControls })

function Controls() {
  const camera = useThree((state) => state.camera)
  const gl = useThree((state) => state.gl)

  // Will only initialize when tree is connected to screen
  return <orbitControls args={[camera, gl.domElement]}>
}

<Suspense>
  <Controls />
  <AsyncComponent />
</Suspense>

Swapping with Args and Primitives

Swapping elements when changing the args or primitive object prop has been improved for structured children like arrays or iterators.

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:

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 (Current Pattern)

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 })

Testing

StrictMode

StrictMode is now correctly inherited from a parent renderer:

diff
<StrictMode>
  <Canvas>
-    <StrictMode>
-      // ...
-    </StrictMode>
+    // ...
  </Canvas>
</StrictMode>

WARNING

This change may affect the behavior of your application. If you encounter anything that worked before and fails now, profile it first in dev and then production. If it works in prod then Strict Mode has flushed out a side-effect in your code.

Act

act is now exported from React itself and can be used for all renderers:

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

const store = await act(async () => createRoot(canvas).render(<App />))
console.log(store.getState())