Back to Blog
React Three Fiber: When Your UI Needs More Than Just DOM
7 min readJul 3, 20262 views

React Three Fiber: When Your UI Needs More Than Just DOM

Pushing pixels on a 2D plane is fine for most applications, but what happens when you need true 3D interactivity, complex visualizations, or even games? React Three Fiber brings the power of Three.js into the React paradigm, making 3D on the web feel surprisingly familiar.

FrontendReactWeb DevelopmentUISoftware Design
Share

by Sunil Band

Why Go 3D? The Limits of Flatland

For years, our frontend world has been dominated by the Document Object Model. We push pixels, manipulate CSS, and orchestrate component trees, all within the comforting confines of a 2D browser canvas. And for most applications – forms, dashboards, content sites – that's perfectly fine. It's performant, accessible, and well-understood.

But what happens when your UI demands more? When you need to visualize complex data in three dimensions, create immersive product configurators, or even build a lightweight game directly in the browser? Suddenly, the DOM starts to feel like a straightjacket. You can fake some 3D with CSS transforms, but it quickly breaks down when you need real perspective, lighting, physics, or GPU-accelerated rendering.

This is where libraries like Three.js come in. Three.js is a powerful JavaScript 3D library that abstracts away the complexities of WebGL, letting you create and render sophisticated 3D graphics. The problem? Integrating it with a modern React application often meant creating a separate canvas, managing imperative Three.js scenes, and then trying to bridge state and events back into your declarative React world. It was clunky, prone to errors, and felt like you were battling two paradigms at once.

Enter React Three Fiber: Declarative 3D

React Three Fiber (R3F) changes all that. It's a React renderer for Three.js, meaning it lets you write your 3D scenes using React components. Instead of imperative calls to scene.add(mesh) or light.position.set(x, y, z), you declare your 3D objects, lights, and cameras as JSX. This brings all the benefits of React's component model – reusability, declarative state management, reconciliation – directly into your 3D world.

Suddenly, your Three.js scene becomes just another part of your React component tree. You can pass props, use hooks, leverage context, and manage state in the same way you do for your HTML DOM components. This drastically lowers the barrier to entry for building complex 3D experiences and makes them feel like a natural extension of your existing React skills.

Getting Started: A Basic Spinning Cube

Let's jump into a quick example. We'll render a simple spinning cube. First, you'll need to install the necessary packages:

bash
npm install three @react-three/fiber @react-three/drei

three is the core Three.js library. @react-three/fiber is the React renderer. @react-three/drei is a collection of useful helpers and abstractions for R3F, often simplifying common tasks.

Now, here's the code for our spinning cube component:

typescript
import React, { useRef, useState } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';

// This is our individual cube component
function Box(props: any) {
  // useRef allows us to access the Three.js mesh object directly
  const meshRef = useRef<THREE.Mesh>(null!); 
  // Hold state for hovered and clicked events
  const [hovered, setHover] = useState(false);
  const [active, setActive] = useState(false);

  // useFrame hook runs every frame, perfect for animations
  useFrame((state, delta) => {
    if (meshRef.current) {
      meshRef.current.rotation.x += delta; // Rotate around X axis based on time delta
      meshRef.current.rotation.y += delta; // Rotate around Y axis
    }
  });

  return (
    // Everything within <mesh> maps directly to Three.js objects
    <mesh
      {...props} // Pass any incoming props (e.g., position)
      ref={meshRef} // Attach our ref to access the mesh
      scale={active ? 1.5 : 1} // Scale up if active
      onClick={() => setActive(!active)} // Toggle active state on click
      onPointerOver={() => setHover(true)} // Set hovered state on mouse over
      onPointerOut={() => setHover(false)} // Clear hovered state on mouse out
    >
      {/* Arguments for BufferGeometry are passed as props to JSX primitive */}
      <boxGeometry args={[1, 1, 1]} /> 
      {/* Material color changes based on hover state */}
      <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
    </mesh>
  );
}

export default function App() {
  return (
    <div style={{ height: '100vh', width: '100vw' }}>
      {/* The Canvas component sets up the WebGL context and Three.js scene */}
      <Canvas>
        {/* Add ambient light so objects are visible */}
        <ambientLight intensity={0.5} />
        {/* Add a directional light for shadows and definition */}
        <directionalLight position={[0, 5, 5]} intensity={1} />
        {/* Our custom Box component */}
        <Box position={[-1.2, 0, 0]} />
        <Box position={[1.2, 0, 0]} />
        {/* OrbitControls from drei allows users to pan, zoom, rotate the camera */}
        <OrbitControls /> 
      </Canvas>
    </div>
  );
}

How it Works:

  1. <Canvas> Component: This is the entry point for your 3D scene. It sets up the WebGL renderer, a Three.js scene, and a camera. All your 3D elements will live inside this canvas. You can pass props to it to configure camera settings, shadows, and more.
  2. Declarative Three.js Objects: Notice how ambientLight, directionalLight, boxGeometry, and meshStandardMaterial are rendered as JSX components. R3F automatically maps these to their corresponding Three.js classes. Props passed to these components directly translate to property assignments on the underlying Three.js objects.
  3. useFrame Hook: This hook is R3F's equivalent of an animation loop. The callback function passed to useFrame will execute on every frame rendered by Three.js. This is where you'll perform animations, update positions, or respond to time-based events. The delta argument gives you the time elapsed since the last frame, crucial for framerate-independent animations.
  4. useRef and Direct Access: While R3F encourages a declarative approach, sometimes you need direct access to the underlying Three.js object (e.g., for imperative animations, or to interact with methods not exposed declaratively). useRef works exactly as in standard React, allowing you to get a reference to the Three.js object created by R3F.
  5. Event Handling: R3F wraps Three.js's raycasting capabilities, allowing you to attach standard DOM-like event handlers (onClick, onPointerOver, onPointerOut) directly to your 3D meshes. This makes interactivity incredibly intuitive.
  6. @react-three/drei: This library is a treasure trove of useful helpers. In our example, OrbitControls gives you instant camera controls (rotate, zoom, pan) with zero effort. drei also offers components for loading models, displaying text, creating realistic shadows, and much more.

Beyond the Cube: Real-World Applications

The spinning cube is a classic, but R3F shines in more complex scenarios:

  • Data Visualization: Imagine interactive 3D graphs, scatter plots, or network diagrams where users can explore data from any angle.
  • Product Configurators: Allow customers to customize products (cars, furniture, shoes) in real-time 3D, changing colors, materials, and accessories.
  • Architectural Walkthroughs: Create immersive virtual tours of buildings or spaces.
  • Educational Tools: Visualize complex scientific concepts or historical events in an engaging 3D environment.
  • Games: While not a full-fledged game engine, R3F is perfectly capable of handling smaller, web-based 3D games and interactive experiences.

The declarative nature makes it easy to bind UI elements to 3D states. Clicking a button in your standard React DOM can change the material of a 3D object, or hovering over a 3D model can trigger a tooltip in your 2D UI. The boundary between 2D and 3D blurs in a very productive way.

Performance and Trade-offs

Anytime you venture into 3D, performance is a primary concern. Three.js and WebGL push the GPU hard, and it's easy to create scenes that tank framerates on less powerful devices. R3F itself is highly optimized; it's a thin wrapper around Three.js and benefits from React's efficient reconciliation algorithm. However, the onus is still on you to build efficient 3D scenes:

  • Geometry Optimization: Use simpler geometries where possible, merge meshes, and instance similar objects.
  • Material Optimization: Limit the number of different materials, use texture atlases, and avoid overly complex shaders.
  • Light Optimization: Too many dynamic lights can be expensive. Consider baking lights or using simpler lighting models.
  • Resource Loading: Load models and textures asynchronously and only when needed.

One potential trade-off is the learning curve. While R3F makes Three.js accessible to React developers, you still need to understand fundamental 3D concepts: scene graphs, cameras, lights, materials, geometries, and transformations. If you're completely new to 3D, there's a fair bit to absorb. However, drei and the excellent R3F community resources go a long way in easing this.

Another point is bundle size. Adding Three.js and R3F will increase your JavaScript bundle size. For simple UIs, this might be overkill. But for applications where 3D is a core feature, the benefits far outweigh the cost.

Wrapping up

React Three Fiber isn't just a gimmick; it's a powerful abstraction that finally brings the best of declarative UI development to the world of real-time 3D graphics. It lets you leverage your existing React knowledge to build immersive and visually rich web experiences that were previously the domain of specialized 3D developers. If your next project demands more than flat rectangles and text, and you want to push the boundaries of what a web application can be, R3F is an essential tool to have in your arsenal.

My advice? Clone the official react-three-fiber examples repository. It's packed with runnable code that showcases everything from basic geometries to complex physics simulations and post-processing effects. Dive in, modify the examples, and see how quickly you can bring your ideas to life in three dimensions.

More from the blog
Available for projectsReady to make something fun 🎈

Ready to build the next system?Wanna build something awesome together?

Currently accepting high-impact opportunities in frontend engineering and scalable web applications.Got a cool idea rattling around? Let's grab a virtual coffee and turn it into something people love. ☕