Skip to content

Objects

Fresh

You can use three.js's entire object catalogue and all properties. When in doubt, always consult the three.js docs.

Declaring Objects

Avoid creating objects imperatively — it forces re-creation on every render:

jsx
// AVOID — these properties will always be re-created
<mesh
  visible
  userData={{ hello: 'world' }}
  position={new THREE.Vector3(1, 2, 3)}
  rotation={new THREE.Euler(Math.PI / 2, 0, 0)}
  geometry={new THREE.SphereGeometry(1, 16, 16)}
  material={new THREE.MeshBasicMaterial({ color: new THREE.Color('hotpink'), transparent: true })}
/>

Instead, define properties declaratively:

jsx
// PREFER — declarative props
<mesh visible userData={{ hello: 'world' }} position={[1, 2, 3]} rotation={[Math.PI / 2, 0, 0]}>
  <sphereGeometry args={[1, 16, 16]} />
  <meshStandardMaterial color="hotpink" transparent />
</mesh>

Constructor Arguments

In three.js, objects are classes that are instantiated. Constructor arguments are passed as an array via args. If args change later on, the object will be reconstructed from scratch.

jsx
<sphereGeometry args={[1, 32]} />

Shortcuts

Set

All properties whose underlying object has a .set() method can directly receive the same arguments that set would take.

  • THREE.Color.set can take a color string, so color="hotpink" works
  • THREE.Vector3 takes multiple arguments, so use an array: position={[100, 0, 0]}
jsx
<mesh position={[1, 2, 3]} />
  <meshStandardMaterial color="hotpink" />

INFO

If you link up an existing object to a property like a THREE.Vector3() to position, R3F will copy the object in most cases by calling .copy() on the target. If you link a material or geometry, it will overwrite because these objects do not have a .set() method.

SetScalar

Properties that have a setScalar method (like Vector3) can be set with a single number:

jsx
// Translates to <mesh scale={[1, 1, 1]} />
<mesh scale={1} />

Piercing into Nested Properties

Use dash-case to reach into nested attributes:

jsx
<mesh rotation-x={1} material-uniforms-resolution-value={[512, 512]} />

Dealing with Non-Scene Objects

You can put non-Object3D primitives (geometries, materials, etc.) into the render tree. They take the same properties and constructor arguments they normally would. They are managed, reactive and auto-dispose. These objects are not technically part of the scene, but they "attach" to a parent which is.

Attach

Use attach to bind objects to their parent. If you unmount the attached object it will be taken off its parent automatically.

jsx
<mesh>
  <meshBasicMaterial attach="material" />
  <boxGeometry attach="geometry" />

INFO

All objects extending THREE.Material receive attach="material", and all objects extending THREE.BufferGeometry receive attach="geometry" automatically. You do not have to type it out!

jsx
<mesh>
  <meshBasicMaterial />
  <boxGeometry />

Deeply Nested Attach

Attach through piercing adds a buffer-attribute to geometry.attributes.position:

jsx
<mesh>
  <bufferGeometry>
    <bufferAttribute attach="attributes-position" args={[v, 3]} />

More Examples

jsx
// Attach bar to foo.a
<foo>
  <bar attach="a" />

// Attach bar to foo.a.b and foo.a.b.c (nested object attach)
<foo>
  <bar attach="a-b" />
  <bar attach="a-b-c" />

// Attach bar to foo.a[0] and foo.a[1] (array attach is just object attach)
<foo>
  <bar attach="a-0" />
  <bar attach="a-1" />

// Attach bar to foo via explicit add/remove functions
<foo>
  <bar attach={(parent, self) => {
    parent.add(self)
    return () => parent.remove(self)
  }} />

// The same as a one liner
<foo>
  <bar attach={(parent, self) => (parent.add(self), () => parent.remove(self))} />

Attaching a Shadow Camera

diff
- <directionalLight
-   castShadow
-   position={[2.5, 8, 5]}
-   shadow-mapSize={[1024, 1024]}
-   shadow-camera-far={50}
-   shadow-camera-left={-10}
-   shadow-camera-right={10}
-   shadow-camera-top={10}
-   shadow-camera-bottom={-10}
- />
+ <directionalLight castShadow position={[2.5, 8, 5]} shadow-mapSize={[1024, 1024]}>
+   <orthographicCamera attach="shadow-camera" args={[-10, 10, 10, -10]} />
+ </directionalLight>

Multi-Materials (Arrays)

Arrays must have explicit order:

jsx
<mesh>
  {colors.map((color, index) => <meshBasicMaterial key={index} attach={`material-${index}`} color={color} />}
</mesh>

Primitives

Use the primitive placeholder to put existing objects into the scene graph. Primitives will not dispose of the object they carry on unmount — you are responsible for disposing.

jsx
const mesh = new THREE.Mesh(geometry, material)

function Component() {
  return <primitive object={mesh} position={[10, 0, 0]} />

WARNING

Scene objects can only ever be added once in three.js. If you want to re-use an existing object, you must clone it first.

Using 3rd-Party Objects

The extend function extends R3F's catalogue of JSX elements:

jsx
import { extend } from '@react-three/fiber'
import { OrbitControls, TransformControls } from 'three-stdlib'
extend({ OrbitControls, TransformControls })

// ...
return (
  <>
    <orbitControls />
    <transformControls />

If using TypeScript, you'll also need to extend the JSX namespace.

Disposal

Freeing resources is a manual chore in three.js, but React Three Fiber will attempt to free resources for you by calling object.dispose() on all unmounted objects.

If you manage assets by yourself, disable automatic disposal with dispose={null}:

jsx
const globalGeometry = new THREE.BoxGeometry()
const globalMaterial = new THREE.MeshBasicMaterial()

function Mesh() {
  return (
    <group dispose={null}>
      <mesh geometry={globalGeometry} material={globalMaterial} />

Shader Material Uniforms

ShaderMaterial, RawShaderMaterial and subclasses keep the material's uniforms object stable. R3F merges incoming uniforms into the existing target instead of replacing it:

jsx
<shaderMaterial
  uniforms={{
    time: { value: time },
    color: { value: color },
  }}
/>

For stable reference to uniforms, use a React ref:

jsx
function Component() {
  const ref = useRef()

  const uniforms = useMemo(
    () => ({
      time: { value: 0 },
      color: { value: color },
    }),
    [color],
  )

  useFrame(({ clock }) => {
    // Mutate the stable uniforms object directly
    ref.current.uniforms.time.value = clock.elapsedTime
  })

  return (
    <mesh>
      <shaderMaterial ref={ref} uniforms={uniforms} />
    </mesh>
  )
}