Skip to content
Branch: master
Find file Copy path
Find file Copy path
3 contributors

Users who have contributed to this file

@drcmda @Mike-Dax @hasparus
172 lines (127 sloc) 6.1 KB

What's new in v4 🎉

There are technically no breaking-changes, except that some of the new features rely on React being up to date (or even experimental). Among countless of bugfixes, little tweaks and additions, these are the major changes:

HTML overlays

Attaching dom content to 3d surfaces is hard, in threejs there are a couple of helpers like CSS2D/3D-Renderer, but you are still supposed to create dom nodes imperatively via createElement. Using the new <Dom/> primitive you can throw dom content right into the scene graph. It will automatically track its position and follow along.

import { Dom } from 'react-three-fiber'

<group position={[100, 10, 0]}>
    <sphereBufferGeometry attach="geometry" />

Here's an example:

Concurrent mode (experimental)

React-three-fiber can opt into reacts new concurrent/async mode. React will render asynchroneously from then on. It will try to keep a steady 60fps loop at all cost, it will schedule, defer or virtualize operations that threaten to blow the budget.

Imagine you are creating assets at runtime, each has a slight setup cost (for instance TextGeometry having to calculate shapes). In blocking mode React or plain threejs, too many of these will eventually create jank. In concurrent mode React will commit as much as it can, and deal with the rest in a manner that leaves the main thread uninterrupted.

You can find a small stress-test here: In that test React is facing 600ms of CPU processing cost, divided between a bunch of components. It will schedule the load away, not a single frame skipped.


With Reacts suspense you can manage async assets, which makes it very easy to create loaders or fallback mechanisms.

import { Suspense } from 'react'

function AsyncResource() {
  const gltf = useLoader(GLTFLoader, "/model.glb")
  return <primitive object={gltf.scene} />

function Startup() {
  // Zoom camera out on start-up, once all assets have been loaded
  useFrame(({ camera }) => {
    camera.zoom = lerp(camera.zoom, 100, 0.1)
  return null

<Canvas concurrent>
  <Suspense fallback={<Dom>loading...</Dom>}>
    <AsyncResource />
    <Startup />

Autogenerated components for better typing

Typescript treats JSX as a DSL for the dom. Custom reconcilers are not considered and this has caused some issues in the past (, especially with elements that exist in both the dom and the threejs namespace, like audio and line. You can now opt out of native elements altogether.

import { Mesh, TorusKnotGeometry, MeshBasicMaterial } from 'react-three-fiber/components'

function TorusKnot() {
  return (
      <TorusKnotGeometry attach="geometry" args={[10, 3, 100, 16]} />
      <MeshBasicMaterial attach="material" color="hotpink" />

Primitive and New

react-three-fiber/components exports Primitive and New to provide better type safety.

import { Primitive, New } from 'react-three-fiber/components'

class A {
  constructor(public foo: number, bar: string) {}

const mesh = new THREE.Mesh()
const geometry = new THREE.SphereGeometry(1, 16, 16)

return (
  <Primitive object={mesh} geometry={geometry} />
  // Type 'null' is not assignable to type 'string'.(2322)
  <New object={A} args={[1, null]} />

Generating components for a specific THREE version

Exported components are generated for the threejs version in react-three-fiber devDependencies. They depend only on names of threejs exports. If exported classes of your threejs version differ, you can either

  1. Use jsx native element
  2. Add it in your code
    import { ThreeFiberComponents } from 'react-three-fiber/components'
    const Thing = ('thing' as any) as ThreeFiberComponents['Thing']
  3. Generate exports using src/components/generateExports.ts

Switch off recursive asset disposal via dispose={null}

If you are working with loaded assets (useLoader or THREE.Loader) you may have noticed that unmounting breaks these assets because react-three-fiber calls .dispose() on all objects that unmount. You can now control recursive disposal yourself, that way you can keep assets alive over route changes.

<group dispose={null}>
  <mesh />
    <mesh />


We are using react-use-measure and @juggle/resize-observer@3.x to detect the actual position of the canvas (which is later necessary for things like raycasting). This ensures that even if the canvas is nested within scroll-areas, everything will work.

X-platform architecture (with targets for react-native, css2d, css3d and svg)

The codebase has been refactored to make creating specific target renderers easier.

import { Canvas, extend, useFrame, useThree } from "react-three-fiber/svg"

  <Canvas style={{ background: "#272730" }} camera={{ position: [0, 0, 50] }}>
      <torusKnotGeometry attach="geometry" args={[10, 3, 100, 16]} />
      <meshBasicMaterial attach="material" color="hotpink" />

Currently you can refer to the following targets:

  • react-three-fiber, webGL canvas
  • react-three-fiber (on react-native), react-native OPENGLES canvas
  • react-three-fiber/svg, renders into an svg
  • react-three-fiber/css2d, threejs Css2dRenderer
  • react-three-fiber/css3d, threejs Css3dRenderer
You can’t perform that action at this time.