Holographic 3D models floating above a desk with JSX code on a monitor and a maine coon cat on the windowsill

React Three Fiber: The Third Dimension Unlocked

The Orange Cat
The Orange Cat

If you have ever wanted to drop a spinning 3D model into a React app and wondered why the setup felt like assembling a particle accelerator, React Three Fiber is the answer you have been looking for. @react-three/fiber is a React renderer for Three.js that maps the entire Three.js API to JSX elements. Instead of writing imperative boilerplate to create scenes, cameras, and render loops, you compose 3D worlds with the same component model you use for buttons and forms. The library does not hide Three.js behind an abstraction wall -- every class, every property, every method is still right there. It just lets you reach it through familiar React patterns like hooks, context, suspense, and refs.

Whether you are building a product configurator for an e-commerce site, an interactive data visualization, a browser game, or a portfolio piece that stops people mid-scroll, React Three Fiber gives you a production-ready foundation with 30,000+ GitHub stars and an ecosystem of companion libraries to prove it.

Why Declarative 3D Matters

Vanilla Three.js is powerful but verbose. Setting up a basic scene requires creating a renderer, appending a canvas, building a camera, adding objects to a scene graph, wiring up a resize listener, and kicking off a render loop -- all imperatively. That works fine for a static demo, but the moment your scene needs to respond to application state, fetch remote data, or share logic across components, the imperative approach starts fighting the React mental model.

React Three Fiber eliminates that friction. A <mesh> in JSX is a Three.js Mesh. A <boxGeometry> is a BoxGeometry. Constructor arguments go in the args prop, properties map directly to component props, and the render loop is handled for you. React's scheduling abilities actually let R3F outperform hand-rolled Three.js at scale, because the reconciler can batch updates and skip unnecessary re-renders.

Feature Rundown

  • Full Three.js Coverage: Every Three.js class is available as a lowercase JSX element. New Three.js releases work immediately without waiting for library updates.
  • Built-in Hooks: useFrame for per-frame logic, useThree for accessing the renderer, scene, and camera, and useLoader for asset loading with caching and suspense.
  • Pointer Events: Click, hover, and drag events work on 3D objects out of the box with raycasting handled automatically.
  • React 19 Support: Version 9 ships with full React 19 compatibility, including bundled reconciler support for React 19.0 through 19.2.
  • React Native Ready: Optional peer dependencies for Expo and React Native mean the same declarative approach works on mobile.
  • Rich Ecosystem: Companion libraries for helpers (@react-three/drei), post-processing effects, physics engines, VR/AR, flexbox layouts in 3D, and accessibility.

Getting Started

Install the library alongside Three.js and React.

npm install @react-three/fiber three
# or
yarn add @react-three/fiber three

That is all you need. The <Canvas> component sets up a WebGL context, a default camera, a scene, and a resize-aware render loop. Everything inside <Canvas> lives in R3F's React reconciler, so standard React patterns apply.

Your First Scene

A Spinning Cube

The classic hello-world for 3D: a colored cube that rotates on every frame.

import { useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import type { Mesh } from "three";

function SpinningBox() {
  const meshRef = useRef<Mesh>(null);

  useFrame((_, delta) => {
    if (meshRef.current) {
      meshRef.current.rotation.x += delta;
      meshRef.current.rotation.y += delta * 0.5;
    }
  });

  return (
    <mesh ref={meshRef}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="royalblue" />
    </mesh>
  );
}

export default function App() {
  return (
    <Canvas>
      <ambientLight intensity={0.4} />
      <directionalLight position={[5, 5, 5]} />
      <SpinningBox />
    </Canvas>
  );
}

The useFrame hook fires before every rendered frame, giving you a delta time value so animations stay smooth regardless of frame rate. Notice that the mesh is updated through a ref rather than state -- this is intentional. Calling setState inside useFrame would trigger React re-renders sixty times per second, which is wasteful. Refs let you mutate Three.js objects directly, keeping the render loop lean.

Reacting to Clicks

Three.js objects inside R3F support pointer events natively. Here is a box that changes color when you click it.

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

function ClickableBox() {
  const [active, setActive] = useState(false);

  return (
    <mesh onClick={() => setActive(!active)} scale={active ? 1.3 : 1}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color={active ? "hotpink" : "orange"} />
    </mesh>
  );
}

export default function App() {
  return (
    <Canvas>
      <ambientLight intensity={0.5} />
      <pointLight position={[10, 10, 10]} />
      <ClickableBox />
    </Canvas>
  );
}

R3F handles raycasting behind the scenes. The onClick event fires only when the user clicks on that specific mesh, not just anywhere on the canvas. You also get onPointerOver, onPointerOut, onPointerMove, and other familiar events, making interactive 3D feel as natural as styling a hover state in CSS.

Loading a 3D Model

Real-world scenes often use GLTF models created in Blender or other tools. The useLoader hook pairs with Three.js loaders to fetch and cache assets.

import { Suspense } from "react";
import { Canvas, useLoader } from "@react-three/fiber";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";

function Spaceship() {
  const gltf = useLoader(GLTFLoader, "/models/spaceship.glb");
  return <primitive object={gltf.scene} scale={0.5} />;
}

export default function App() {
  return (
    <Canvas>
      <ambientLight />
      <directionalLight position={[2, 4, 3]} />
      <Suspense fallback={null}>
        <Spaceship />
      </Suspense>
    </Canvas>
  );
}

useLoader integrates with React Suspense, so you can show a loading fallback while the model downloads. Once loaded, the model is cached -- navigating away and coming back will not trigger a second network request.

Beyond the Basics

Accessing the Rendering Engine

The useThree hook exposes the entire R3F state model: the renderer, the scene, the camera, viewport dimensions, and more.

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

function CameraLogger() {
  const { camera, viewport, size } = useThree();

  console.log("Camera position:", camera.position);
  console.log("Viewport:", viewport.width, "x", viewport.height);
  console.log("Canvas pixel size:", size.width, "x", size.height);

  return null;
}

The state is reactive. When the browser window resizes, components consuming useThree automatically re-render with updated dimensions. This makes responsive 3D layouts straightforward -- scale objects relative to the viewport, adjust camera frustums, or swap between mobile and desktop scene configurations without manual event listeners.

Custom Render Priorities

Sometimes you need fine-grained control over the render loop. The useFrame hook accepts a render priority that determines when your callback runs relative to the default render pass.

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

function PhysicsStep() {
  useFrame((state) => {
    // Runs before default render (priority < 0)
    // Update physics simulation here
  }, -1);

  return null;
}

function PostProcess() {
  useFrame((state) => {
    // Runs after default render (priority > 0)
    // Apply post-processing effects
  }, 1);

  return null;
}

Priority values let you build layered systems -- physics before rendering, post-processing after -- without resorting to manual render-loop orchestration. If you set a priority of 1 or higher, the default render pass is disabled and you take full manual control, which is useful for custom multi-pass rendering setups.

Extending with Custom Three.js Classes

When you need a Three.js class that is not part of the core (like OrbitControls), the extend function registers it for use in JSX.

import { useRef } from "react";
import { Canvas, extend, useThree, useFrame } from "@react-three/fiber";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

extend({ OrbitControls });

function Controls() {
  const { camera, gl } = useThree();
  const controlsRef = useRef(null);

  useFrame(() => {
    controlsRef.current?.update();
  });

  return <orbitControls ref={controlsRef} args={[camera, gl.domElement]} />;
}

export default function App() {
  return (
    <Canvas>
      <ambientLight />
      <mesh>
        <torusKnotGeometry args={[1, 0.3, 128, 32]} />
        <meshNormalMaterial />
      </mesh>
      <Controls />
    </Canvas>
  );
}

After calling extend, the class becomes available as a camelCase JSX element. This pattern works for any Three.js addon -- custom shaders, special geometries, third-party loaders -- keeping the declarative surface intact.

The Ecosystem at a Glance

React Three Fiber is the center of a thriving collection of libraries maintained by the Poimandres collective:

  • @react-three/drei: Over 100 ready-made helpers including cameras, controls, shapes, text rendering, environment maps, and performance monitors. If you find yourself building something common, Drei probably already has it.
  • @react-three/postprocessing: Bloom, depth of field, vignette, chromatic aberration, and other post-processing effects with automatic merging for performance.
  • @react-three/rapier and @react-three/cannon: Physics engines wrapped in React components for rigid-body simulations, colliders, and constraints.
  • @react-three/xr: WebXR support for building VR and AR experiences.
  • gltfjsx: A CLI tool that converts GLTF models into clean, typed React components.

This ecosystem means you rarely need to drop down to raw Three.js imperative code, even for advanced features.

Conclusion

React Three Fiber takes the power of Three.js and wraps it in the developer experience React developers already know and love. The component model, the hooks, the suspense integration, the pointer events -- they all work exactly the way you would expect, just in three dimensions instead of two. With version 9.5 shipping React 19 compatibility and version 10 on the horizon, @react-three/fiber continues to be the definitive way to bring 3D to React applications. If your next project needs a spinning logo, an interactive product viewer, or an entire virtual world, you now have a renderer that makes building it feel like writing any other React component.