You're viewing the v10 deployment.

Hooks

Hooks are the heart of react-three-fiber

Hooks allow you to tie or request specific information to your component. For instance, components that want to participate in the renderloop can use useFrame, components that need to be informed of three.js specifics can use useThree and so on. All hooks clean up after themselves once the component unmounts.

Note

Hooks can only be used inside the Canvas element because they rely on context!

❌ You cannot expect something like this to work:

import { useThree } from '@react-three/fiber'

function App() {
  const { size } = useThree() // This will just crash
  return (
    <Canvas>
      <mesh>

✅ Do this instead:

function Foo() {
  const { size } = useThree()
  ...
}

function App() {
  return (
    <Canvas>
      <Foo />

useThree

This hook gives you access to the state model which contains the default renderer, the scene, your camera, and so on. It also gives you the current size of the canvas in screen and viewport coordinates.

import { useThree } from '@react-three/fiber'

function Foo() {
  const state = useThree()

The hook is reactive, if you resize the browser for instance, you get fresh measurements, same applies to any of the state objects that may change.

state properties

PropDescriptionType
rendererThe renderer instance — type depends on entry point (WebGL, WebGPU, or the union for the default entry)R3FRenderer
glDeprecated — alias of renderer, typed as WebGLRenderer for back-compat. Use rendererTHREE.WebGLRenderer
sceneSceneTHREE.Scene
cameraCameraTHREE.PerspectiveCamera
frustumCamera frustum for visibility checks, auto-updated each frameTHREE.Frustum
autoUpdateFrustumWhether frustum auto-updates each frame (default: true)boolean
raycasterDefault raycasterTHREE.Raycaster
pointerContains updated, normalized, centric pointer coordinatesTHREE.Vector2
mouseNote: this is deprecated, use pointer instead! Normalized event coordinatesTHREE.Vector2
frameloopRender mode: always, demand, neveralways, demand, never
performanceSystem regression{ current: number, min: number, max: number, debounce: number, regress: () => void }
sizeCanvas size in pixels{ width: number, height: number, top: number, left: number }
viewportCanvas viewport size in three.js units. Note: This is different from gl.getViewport which returns the drawbuffer size{ width: number, height: number, initialDpr: number, dpr: number, factor: number, distance: number, aspect: number, getCurrentViewport: (camera?: Camera, target?: THREE.Vector3, size?: Size) => Viewport }
xrXR interface, manages WebXR rendering{ connect: () => void, disconnect: () => void }
setAllows you to set any state property(state: SetState<RootState>) => void
getAllows you to retrieve any state property non-reactively() => GetState<RootState>
invalidateRequest a new render, given that frameloop === 'demand'() => void
advanceAdvance one tick, given that frameloop === 'never'(timestamp: number, runGlobalEffects?: boolean) => void
setSizeResize the canvas(width: number, height: number, top?: number, left?: number) => void
setDprSet the pixel-ratio(dpr: number) => void
setFrameloopShortcut to set the current render mode(frameloop?: 'always', 'demand', 'never') => void
setEventsShortcut to setting the event layer(events: Partial<EventManager<any>>) => void
onPointerMissedResponse for pointer clicks that have missed a target() => void
eventsPointer-event handling{ connected: TargetNode, handlers: Events, connect: (target: TargetNode) => void, disconnect: () => void }

Selector

You can also select properties, this allows you to avoid needless re-render for components that are interested only in particulars. Reactivity does not include deeper three.js internals!

// Will only trigger re-render when the default camera is exchanged
const camera = useThree((state) => state.camera)
// Will only re-render on resize changes
const viewport = useThree((state) => state.viewport)
// ❌ You cannot expect reactivity from three.js internals!
const zoom = useThree((state) => state.camera.zoom)

Reading state from outside of the component cycle

function Foo() {
  const get = useThree((state) => state.get)
  ...
  get() // Get fresh state from anywhere you want

Exchanging defaults

function Foo() {
  const set = useThree((state) => state.set)
  ...
  useEffect(() => {
    set({ camera: new THREE.OrthographicCamera(...) })
  }, [])

useFrame

This hook allows you to execute code on every rendered frame, like running effects, updating controls, and so on. You receive the state (same as useThree) and the frame delta. The callback runs inside a shared render-loop; when the component unmounts it is unsubscribed automatically.

import { useFrame } from '@react-three/fiber'

function Foo() {
  useFrame((state, delta, xrFrame) => {
    // This function runs at the native refresh rate inside of a shared render-loop
  })
Note

There is no state.clock in v10. Timing comes through the frame state instead: state.delta (seconds since last frame), state.elapsed (seconds since the first frame), state.time (the RAF timestamp), state.frame (frame count), and state.overstep (how far, in [0, 1), the loop is into the next fixed physics step). For example, useFrame((state) => (mesh.rotation.y = state.elapsed)).

Caution

Be careful about what you do inside useFrame! You should never setState in there! Your calculations should be slim and you should mind all the commonly known pitfalls when dealing with loops in general, like re-use of variables, etc.

useFrame accepts an options object for phase placement, ordering, fps throttling, and pause/resume — and the priority/render-takeover behavior below is part of a larger scheduler. See Frame Loop & Scheduler for the full API.

Jobs in the physics phase run on a fixed 1/60 s step rather than once per frame: useFrame((_, delta) => world.step(delta), { phase: 'physics' }) is called zero or more times a frame, always with delta equal to the step. See Fixed timestep and interpolation.

Taking over the render-loop

In v10 the render loop is phase-based. R3F runs its own default render job in the render phase. The moment you register any job in the render phase, R3F steps aside and that default render is skipped — it becomes your responsibility to render. The default render automatically resumes when your render-phase job unmounts.

import { useFrame } from '@react-three/fiber'

function TakeOver() {
  // Registering a job in the 'render' phase disables the default render
  useFrame(({ renderer, scene, camera }) => {
    renderer.render(scene, camera)
  }, { phase: 'render' })
}

To order callbacks within a phase, name the dependency with before/after (by job id or phase name). If one job has to run before or after another, say so explicitly:

useFrame(() => {}, { id: 'a', phase: 'update' })
useFrame(() => {}, { phase: 'update', after: 'a' }) // runs after job 'a'
Note

A bare numeric second argument (useFrame(fn, 1)) is deprecated and is not an ordering mechanism. A positive number keeps its v9 meaning: it disables the default render (render takeover) and emits a deprecation warning. A negative number also warns, because v10 runs higher numbers first — the reverse of v9. Use { phase: 'render' } for render takeover and before/after for ordering. { priority } exists for legacy code and the rare loose tie-break; don't rely on it for order that matters. See Frame Loop & Scheduler → Ordering jobs and Render-phase takeover for details.

useLoader

This hook loads assets and suspends for easier fallback- and error-handling. It can take any three.js loader as its first argument: GLTFLoader, OBJLoader, TextureLoader, FontLoader, etc. It is based on React.Suspense, so fallback-handling and error-handling happen at the parental level.

import { Suspense } from 'react'
import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'

function Model() {
  const result = useLoader(GLTFLoader, '/model.glb')
  // You don't need to check for the presence of the result, when we're here
  // the result is guaranteed to be present since useLoader suspends the component
  return <primitive object={result.scene} />
}

function App() {
  return (
    <Suspense fallback={<FallbackComponent /> /* or null */}>
      <Model />
    </Suspense>
  )
}
Tip

Internally, useLoader relies on suspend-react.

Note

Assets loaded with useLoader are cached by default. The urls given serve as cache-keys. This allows you to re-use loaded data everywhere in the component tree.

Warning

Be very careful with mutating or disposing of loaded assets, especially when you plan to re-use them. Refer to the automatic disposal section in the API.

Loader extensions

You can provide a callback as the third argument if you need to configure your loader:

import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'

useLoader(GLTFLoader, url, (loader) => {
  const dracoLoader = new DRACOLoader()
  dracoLoader.setDecoderPath('/draco-gltf/')
  loader.setDRACOLoader(dracoLoader)
})

Loading multiple assets at once

It can also make multiple requests in parallel:

const [bumpMap, specMap, normalMap] = useLoader(TextureLoader, [url1, url2, url2])

Loading status

You can get the loading status from a callback you provide as the fourth argument. Though consider alternatives like THREE.DefaultLoadingManager or better yet, Drei's loading helpers.

useLoader(loader, url, extensions, (xhr) => {
  console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
})

Custom cache keys

By default the request URL is also the cache key. When the URL changes on every request — for example signed or authenticated URLs — that would defeat caching and reload the same asset. Pass an explicit cache key as the fifth argument to load from the changing URL but cache under a stable key:

const model = useLoader(GLTFLoader, signedUrl, undefined, undefined, '/model.glb')

preload and clear accept the same cache key so the entries line up:

useLoader.preload(GLTFLoader, signedUrl, undefined, undefined, '/model.glb')
useLoader.clear(GLTFLoader, signedUrl, '/model.glb')

Special treatment of GLTFLoaders and all loaders that return a scene prop

If a result.scene prop is found the hook will automatically create a object & material collection: { nodes, materials }. This lets you build immutable scene graphs selectively. You can also specifically alter the data without having to traverse it. GLTFJSX specifically relies on this data.

const { nodes, materials } = useLoader(GLTFLoader, url)

Pre-loading assets

You can pre-load assets in global space so that models can be loaded in anticipation before they're mounted in the component tree.

useLoader.preload(GLTFLoader, '/model.glb' /* extensions */)

useTexture

In v10, useTexture is exported from @react-three/fiber. It loads textures through TextureLoader with Suspense support, without requiring you to import the loader. Pass a URL, an array of URLs, or a record of names to URLs; the returned textures mirror that input shape.

import { useTexture } from '@react-three/fiber'

function Material() {
  const props = useTexture({
    map: '/color.jpg',
    normalMap: '/normal.jpg',
  })
  return <meshStandardMaterial {...props} />
}

The optional second argument accepts { onLoad, cache } or an onLoad callback. cache defaults to true, enrolling the textures in the registry for access through useTextures; pass { cache: false } to skip enrollment. See Loading Textures for examples and registry lifetime details.

useEnvironment

Loads an environment map with Suspense and returns a texture ready for scene.environment or scene.background, with its mapping and color space already set. It is what <Environment> and the Canvas background prop use under the hood.

import { useEnvironment } from '@react-three/fiber'

function Lighting() {
  const texture = useEnvironment({ files: '/studio.hdr' }) // or { preset: 'sunset' }
  return <primitive attach="environment" object={texture} />
}

Options: files, path, preset (one of apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse), colorSpace, and extensions(loader) to configure the loader. files is a single .hdr, .exr or Ultra HDR .jpg/.jpeg (decoded by three's UltraHDRLoader), or an array of six cube faces (.hdr faces load as an HDR cubemap). Each decoder is downloaded only when a file needs it. useEnvironment.preload(options) and useEnvironment.clear({ files, preset }) warm and evict the cache.

Note

The split gain map format (['sdr.webp', 'gainmap.webp', 'metadata.json']) is no longer supported. Use a single-file Ultra HDR .jpg, or drei's <Environment> if you need the split format.

useGraph

Convenience hook which creates a memoized, named object/material collection from any Object3D.

import { useLoader, useGraph } from '@react-three/fiber'

function Model(url) {
  const scene = useLoader(OBJLoader, url)
  const { nodes, materials } = useGraph(scene)
  return <mesh geometry={nodes.robot.geometry} material={materials.metal} />
}