mrkeyoor.com_
Sat 08 Aug 21:00 UTC
npmWeb Frontendupdated 08 Aug 2026

@react-three/fiber

@react-three/fiber is a custom React renderer for Three.js. It maps JSX such as mesh, boxGeometry, lights, and materials to real Three.js objects, owns the WebGL render loop, and connects React events and lifecycle to a 3D scene. Hooks expose the renderer, camera, scene, pointer, clock, viewport, asset loading, and per-frame updates. It does not replace Three.js knowledge or provide ready-made models, controls, physics, or post-processing; those come from Three.js and the wider React Three Fiber ecosystem.

Verdict

The best-supported way to build a substantial Three.js scene as a React application, provided the team knows both layers and respects GPU constraints. It is excessive for decorative 3D and a poor shortcut around learning Three.js.

API stability4/5Canvas, useFrame, useThree, useLoader, JSX-mapped Three.js objects, and pointer events have remained the core model across releases. Version compatibility is explicit but consequential: Fiber 8 pairs with React 18 and Fiber 9 with React 19, while the registry already carries alpha and canary tags for v10. Three.js changes also appear immediately in JSX because Fiber maps its constructors dynamically.
Docs5/5The documentation site covers installation, Canvas defaults, hooks, objects, events, testing, TypeScript, native use, scaling, and performance pitfalls with runnable examples. It is candid about context-only hooks, shared useLoader assets, avoiding setState in useFrame, manual resize for createRoot, and disposal. Readers still need the separate Three.js reference because Fiber intentionally does not duplicate it.
Maintenance5/5Version 9.7.0 was published on July 31, 2026, the repository was pushed on August 7, and GitHub reports 40 open issues and pull requests together on a project with 31,675 stars. The repository is not archived, active prerelease channels exist for the next major, and the project continues to track current React and Three.js releases closely.
Ecosystem5/5The npm endpoint reports 4,990,191 downloads for the measured week. The README links maintained packages for helpers, GLTF conversion, post-processing, UI, testing, workers, flex layout, XR, physics, accessibility, path tracing, animation, controls, math, and visual editing. This is a genuine platform around the renderer, although each addition increases version and performance coordination.

Use it if

  • Your product already uses React and the 3D scene benefits from reusable components, state, Suspense, and pointer events
  • You want Three.js objects expressed declaratively while retaining access to every underlying object and API
  • You need the large pmndrs ecosystem for controls, model helpers, physics, XR, post-processing, testing, and GLTF-to-JSX tooling
  • You want one renderer that supports browser WebGL and an Expo React Native entry point
Skip it if

Setup reality

For the web, install three, @types/three, and @react-three/fiber. Version 9.7.0 requires React 19 and Three.js >=0.156; the README's compatibility rule is simple but easy to miss: Fiber 8 is for React 18, Fiber 9 is for React 19. React DOM is an optional peer because the native and custom-root paths do not always use it. Canvas supplies the renderer, scene, camera, resize tracking, raycaster, event manager, and a continuously running frame loop. Hooks such as useFrame and useThree crash outside Canvas context. This is browser or native rendering code, so server frameworks need a client boundary and often a dynamic import that avoids server execution. Models and textures are separate network assets: configure public paths, CORS, compression, loading fallbacks, and an error boundary. useLoader caches by URL, which is useful until code mutates or disposes a shared asset. The default loop renders continuously; static scenes should use frameloop='demand' and call invalidate() after imperative changes. Never route every animation frame through React state, and reuse vectors, geometries, and materials to limit garbage collection and GPU compilation. The browser build is 51.8 KB gzipped before Three.js or ecosystem helpers. React Native uses @react-three/fiber/native plus compatible Expo, expo-gl, asset, and file-system packages, and Metro may need glb, image, and cjs extensions. WebGL failure and context loss still need a useful DOM fallback.

Patterns

Render a lit mesh in Canvasrender-basic-scene

import { Canvas } from '@react-three/fiber';

export function Scene() {
  return (
    <Canvas camera={{ position: [0, 0, 5], fov: 50 }}>
      <ambientLight intensity={0.5} />
      <directionalLight position={[3, 4, 5]} intensity={2} />
      <mesh>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color='orange' />
      </mesh>
    </Canvas>
  );
}

Canvas creates the renderer, scene, perspective camera, resize handling, and event layer; it must run in a client environment.

Animate with frame deltaanimate-mesh

import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import type { Mesh } from 'three';

function Spinner() {
  const ref = useRef<Mesh>(null!);
  useFrame((_, delta) => {
    ref.current.rotation.y += delta;
  });
  return <mesh ref={ref}><boxGeometry /><meshNormalMaterial /></mesh>;
}

Mutate the Three.js object in useFrame and scale motion by delta; setting React state every frame adds avoidable scheduling work.

Subscribe to one Canvas state valueselect-renderer-state

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

function CameraInfo() {
  const camera = useThree(state => state.camera);
  const width = useThree(state => state.size.width);
  return null;
}

useThree only works below Canvas. Selectors reduce React updates, but deep Three.js fields such as camera.zoom are not reactive.

Load a GLTF model with Suspenseload-gltf-model

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

function Model() {
  const gltf = useLoader(GLTFLoader, '/models/robot.glb');
  return <primitive object={gltf.scene} />;
}

<Canvas><Suspense fallback={null}><Model /></Suspense></Canvas>;

useLoader suspends and caches by URL. Put error handling above Canvas and do not dispose or mutate a cached shared scene casually.

Preload a model before it mountspreload-asset

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

useLoader.preload(GLTFLoader, '/models/robot.glb');

Preload uses the same URL-keyed cache as useLoader; the URL and loader configuration must match the later request.

Handle hover and click on a meshhandle-pointer-events

function PickableBox() {
  const [hovered, setHovered] = useState(false);
  return (
    <mesh
      onClick={event => { event.stopPropagation(); select(event.object.uuid); }}
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}>
      <boxGeometry />
      <meshStandardMaterial color={hovered ? 'hotpink' : 'gray'} />
    </mesh>
  );
}

Three-dimensional events bubble through intersected objects; stopPropagation() also prevents farther intersections from receiving the event.

Render only when a static scene changesrender-on-demand

function NudgeableBox() {
  const ref = useRef<THREE.Mesh>(null!);
  const invalidate = useThree(state => state.invalidate);
  return (
    <mesh ref={ref} onClick={() => { ref.current.position.x += 1; invalidate(); }}>
      <boxGeometry />
      <meshNormalMaterial />
    </mesh>
  );
}

<Canvas frameloop='demand'><NudgeableBox /></Canvas>;

invalidate() schedules a frame rather than rendering immediately; declarative prop changes are detected, but imperative mutations need this signal.

Share GPU resources across meshesreuse-geometry-material

const geometry = new THREE.SphereGeometry(1, 32, 32);
const material = new THREE.MeshStandardMaterial({ color: 'royalblue' });

function Pair() {
  return (
    <>
      <mesh geometry={geometry} material={material} position={[-2, 0, 0]} />
      <mesh geometry={geometry} material={material} position={[2, 0, 0]} />
    </>
  );
}

Reusing geometry and material avoids duplicate GPU setup; globally created colors rely on current Three.js color-management settings.

Render many objects with one draw callinstance-repeated-meshes

function Instances({ count = 1000 }) {
  const ref = useRef<THREE.InstancedMesh>(null!);
  useLayoutEffect(() => {
    const object = new THREE.Object3D();
    for (let i = 0; i < count; i++) {
      object.position.set(i % 50, Math.floor(i / 50), 0);
      object.updateMatrix();
      ref.current.setMatrixAt(i, object.matrix);
    }
    ref.current.instanceMatrix.needsUpdate = true;
  }, [count]);
  return <instancedMesh ref={ref} args={[undefined, undefined, count]}><boxGeometry /><meshBasicMaterial /></instancedMesh>;
}

Set instanceMatrix.needsUpdate after changing matrices; instancing trades per-object JSX flexibility for far fewer draw calls.

Reuse intrinsic mesh props in TypeScripttype-mesh-component

import type { ThreeElements } from '@react-three/fiber';

type BoxProps = ThreeElements['mesh'] & { color?: string };

function Box({ color = 'orange', ...props }: BoxProps) {
  return <mesh {...props}><boxGeometry /><meshStandardMaterial color={color} /></mesh>;
}

ThreeElements derives JSX props from current Three.js classes, including event types and constructor-backed properties.

Register a Three.js class for JSXextend-custom-object

import { extend } from '@react-three/fiber';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

extend({ OrbitControls });

function Controls() {
  const { camera, gl } = useThree();
  return <orbitControls args={[camera, gl.domElement]} />;
}

Custom classes need extend() registration and TypeScript module augmentation for typed intrinsic JSX; @react-three/drei supplies ready-made controls with cleanup.

Show content when WebGL is unavailableprovide-webgl-fallback

<Canvas fallback={<img src='/product-static.jpg' alt='Product view' />}>
  <ProductScene />
</Canvas>

The fallback covers renderer creation failure, but an error boundary is still needed for later context crashes and asset errors.

Alternatives

PackageRegistryPick it when
threenpmChoose plain Three.js when you want direct scene and render-loop control without React reconciliation
@babylonjs/corenpmChoose Babylon.js when you want a batteries-included 3D engine with its own inspector, physics integrations, and tooling
aframenpmChoose A-Frame when declarative HTML and a component system are a better fit for a web-first XR experience