` → its material). Props apply to the instance; shorthand values work anywhere three has a `.set()`: `position={[1, 2, 3]}`, `position={new THREE.Vector3(1, 2, 3)}`, `position={5}` (setScalar), `color="#d9915b"`. Dashed props pierce (`rotation-x`, `material-color`, `geometry-attributes-position`); `attach="material-0"` attaches into an array index.
`args` is the constructor argument list; changing it rebuilds the object from scratch. Geometries and materials attach to their parent mesh automatically — no `attach` needed.
```jsx
function Torus() {
return (
console.log(self.type)}
/>
);
}
```
Mount an object you already hold with ``; one object per primitive (fiber keeps its per-object state on the object), and clone for copies. Fiber never disposes primitives — their lifetime is yours. `dispose={null}` on any element opts it out of unmount disposal. Register classes outside the three namespace with `extend`:
```jsx
import { extend } from "@react-three/fiber";
import { Water } from "three/addons/objects/Water.js";
extend({ Water });
// then render anywhere in the Canvas
```
In TypeScript the tags `line`, `audio`, `source` and `path` collide with DOM types; write `threeLine`, `threeAudio`, `threeSource`, `threePath` (fiber strips the prefix and resolves the three class).
## Hooks
`useFrame(callback, renderPriority?)` subscribes to the render loop; it runs before the scene renders:
```jsx
import { useRef } from "react";
import { useFrame } from "@react-three/fiber";
function Spinner({ radius = 2 }) {
const ref = useRef();
useFrame((state, delta) => {
ref.current.rotation.y += delta;
ref.current.position.x = Math.sin(state.clock.elapsedTime) * radius;
});
return (
);
}
```
Subscribers with a positive `renderPriority` take rendering into their own hands — the Canvas stops rendering, and your callback must call `state.gl.render(state.scene, state.camera)` itself. At default priority, calling `gl.render` yourself double-renders.
`useThree(selector?)` reads Canvas state reactively; with no selector it returns the whole state (`gl`, `camera`, `scene`, `raycaster`, `pointer`, `size`, `viewport`, `invalidate`, `setDpr`, `setFrameloop`, `controls`, `performance`):
```jsx
import { useThree } from "@react-three/fiber";
const camera = useThree((state) => state.camera);
const invalidate = useThree((state) => state.invalidate);
```
Inside `useFrame` use the `state` argument instead — it is the same state without a re-render. `state.clock` is a `THREE.Clock` that fiber 9.8 creates, so every Canvas logs three's Clock deprecation warning; it does not come from your code.
`useLoader(ClassOrInstance, url, extensions?, onProgress?)` suspends until any three loader resolves and caches by loader and URL; its caller must sit under a Suspense boundary. The extensions callback runs on the one loader instance fiber keeps per class, so a setting it makes (a path, a decoder) applies to every later `useLoader` call with that class. `useGraph(object)` builds named lookup maps (`{ nodes, materials, meshes }`) from an object tree:
```jsx
import { useGraph, useLoader } from "@react-three/fiber";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
function Model({ url }) {
const gltf = useLoader(GLTFLoader, url, (loader) => loader.setPath("assets/"));
const { nodes } = useGraph(gltf.scene);
return ;
}
```
`useLoader.preload(Class, url)` warms the cache outside React; `useLoader.clear(Class, url)` drops an entry.
## Pointer Events
Objects with handlers are raycast on each pointer event (pointer moves test only objects with move, over, out, enter or leave handlers); handlers fire nearest first, bubbling from the hit object up its parents. The event carries the intersection plus helpers: `object`, `eventObject` (the object that registered the handler), `point`, `distance`, `face`, `instanceId`, `intersections`, `ray`, `camera`, `unprojectedPoint`, `delta` (pixels travelled since pointerdown, for click-like events), `nativeEvent`. Handlers: `onClick`, `onDoubleClick`, `onContextMenu`, `onWheel`, `onPointerUp`, `onPointerDown`, `onPointerMove`, `onPointerOver`, `onPointerOut`, `onPointerEnter`, `onPointerLeave`, `onPointerMissed` (`onPointerCancel` and `onLostPointerCapture` are typed but fiber never calls them on objects).
```jsx
import { useState } from "react";
function Pickable() {
const [hovered, setHovered] = useState(false);
return (
{
event.stopPropagation(); // underlying objects along the ray no longer receive it
console.log(event.object.name, event.distance, event.point, event.instanceId);
}}
onPointerOver={(event) => {
event.stopPropagation();
setHovered(true);
}}
onPointerOut={() => setHovered(false)}
onPointerDown={(event) => {
event.target.setPointerCapture(event.pointerId); // this pointer's events reach the mesh off its surface until release
}}
onPointerUp={(event) => {
event.target.releasePointerCapture(event.pointerId);
}}
onPointerMissed={() => console.log("clicked elsewhere")}
>
);
}
```
Pointer events work under any renderer: the default raycaster tests CPU geometry in `intersectObject(object, true)`. Pass `raycaster={{ params: { Line: { threshold: 0.1 } } }}` to tune it.
## Loading Assets
Loading hooks suspend; put the boundary above them and the fallback shows while they load.
```jsx
import { Suspense } from "react";
import { Canvas } from "@react-three/fiber";
import { useGLTF, useProgress, Environment } from "@react-three/drei";
function Model({ url }) {
const { scene, animations, nodes, materials } = useGLTF(url);
return ;
}
function Hud() {
const { active, progress, errors } = useProgress(); // tracks three's DefaultLoadingManager
return {active ? `${progress.toFixed(0)}%` : errors.length > 0 ? "failed" : "done"}
;
}
export function App() {
return (
<>
>
);
}
```
- `useGLTF(url)` loads through the GLTFLoader drei ships with (three-stdlib), with Draco and meshopt enabled by default; the Draco decoder comes from a pinned gstatic URL (`useGLTF.setDecoderPath` retargets it, `useGLTF(url, false)` disables it). `useGLTF.preload(url)` warms the cache. The result carries `scene`, `animations`, `cameras` plus `nodes`/`materials`/`meshes` lookup maps. For several copies of a skinned model, clone it once per component with `useMemo(() => SkeletonUtils.clone(scene), [scene])` (`import * as SkeletonUtils from "three/addons/utils/SkeletonUtils.js"`) and mount each clone in its own primitive.
- `useTexture(input)` loads one URL, an array (`const [a, b] = useTexture([x, y])`) or a keyed object; fiber sets sRGB color space on LDR (RGBA8) color maps assigned through props — `map`, `emissiveMap`, `envMap` among them — so color maps work without manual `colorSpace`, and scalar maps keep NoColorSpace.
- `` compiles every material once, when it mounts (with `all`, hidden objects' too), and renders the scene into a cube target to upload textures, trading a one-time stall for no first-view hitches; put it inside the Suspense boundary after the assets, or it runs before they load.
- `useProgress` is a plain store, so the Hud above reads it outside the Canvas in the DOM.
## drei Helpers
drei ships many more helpers than these; only this subset is verified here, and every helper imports from `@react-three/drei`.
Controls: `OrbitControls` constructs the controls class drei bundles, connects it to the event source (the Canvas wrapper by default), updates it every frame before user callbacks, calls `invalidate()` on change (so it works with `frameloop="demand"`) and turns damping on by default. `makeDefault` stores it in `state.controls` (Bounds and other helpers pick it up); `regress` drops the adaptive-performance value while dragging. Props pass straight through to the controls instance:
```jsx
import { OrbitControls } from "@react-three/drei";
```
`CameraControls` wraps the camera-controls library the same way (ref methods like `fitToBox` come from that library, and it updates with the frame delta).
Scene dressing:
```jsx
import {
ContactShadows, Environment, Lightformer, Text, Html, Center, Bounds,
} from "@react-three/drei";
export function Dressing({ overlay }) { // overlay: a ref to a DOM element laid over the Canvas
return (
three.js
dom overlay anchored to the scene
);
}
```
- `ContactShadows` renders the scene from below into a depth target and blurs it — a soft contact shadow plane under objects near the floor, with no shadow-map setup; set `frames={1}` to bake it once for still scenes.
- `Lightformer` is a card of light — a mesh with a basic material, `form` `circle`, `ring`, `rect` or `box` — meant to sit inside an `` children scene that bakes into reflections.
- `Text` renders SDF text (troika) with layout props `maxWidth`, `textAlign`, `lineHeight`, `letterSpacing`, `characters`; it suspends while the font loads.
- `Html` projects a DOM node into the scene; `transform` renders it as a 3D-rotated overlay instead of a screen-facing one. `occlude` (`true` or `"raycast"`) hides it while geometry blocks the ray to its anchor, so never anchor it inside a mesh; `occlude="blending"` hides it by depth through a ShaderMaterial (WebGL only). With fiber 9.8, drei 10.7 and React 19.2, an `Html` that mounts in the same commit as a default (WebGLRenderer) Canvas comes up empty, and so does one that remounts with its Canvas; pass `portal` a ref to a DOM element laid over the canvas, which keeps its container fixed, as the Dressing does: `` after the Canvas, inside a `position: relative` wrapper.
- `Center` recenters children on their bounding box (`top`/`bottom`/`left`/`right`/`front`/`back` align to an edge, `precise={false}` measures per-object bounds instead of every vertex, `onCentered` reports the box). It measures vertex positions, so it misplaces drei `Text`, whose geometry is a unit quad; place Text with `anchorX`, `anchorY` and `position` instead.
- `Bounds` fits and frames the camera around its children (`fit`, `clip` adjusts near/far, `observe` refits on resize); `useBounds()` returns its imperative API (`refresh`, `reset`, `moveTo`, `lookAt`, `fit`, `clip`).
## Performance
Render on demand: nothing changes, nothing renders.
```jsx
import { useEffect, useRef } from "react";
import { useThree } from "@react-three/fiber";
function OnDemand() {
const ref = useRef();
const invalidate = useThree((state) => state.invalidate);
// under