--- name: react-three-fiber description: Build declarative 3D scenes with React Three Fiber (R3F) - a React renderer for Three.js. Use when building interactive 3D experiences in React applications with component-based architecture, state management, and reusable abstractions. Ideal for product configurators, portfolios, games, data visualization, and immersive web experiences. --- # React Three Fiber ## Overview React Three Fiber (R3F) is a React renderer for Three.js that brings declarative, component-based 3D development to React applications. Instead of imperatively creating and managing Three.js objects, you build 3D scenes using JSX components that map directly to Three.js objects. **When to Use This Skill**: - Building 3D experiences within React applications - Creating interactive product configurators or showcases - Developing 3D portfolios, galleries, or storytelling experiences - Building games or simulations in React - Adding 3D elements to existing React projects - When you need state management and React hooks with 3D graphics - When working with React frameworks (Next.js, Gatsby, Remix) **Key Benefits**: - **Declarative**: Write 3D scenes like React components - **React Integration**: Full access to hooks, context, state management - **Reusability**: Create and share 3D component libraries - **Performance**: Automatic render optimization and reconciliation - **Ecosystem**: Works with Drei helpers, Zustand, Framer Motion, etc. - **TypeScript Support**: Full type safety for Three.js objects --- ## Core Concepts ### 1. Canvas Component The `` component sets up a Three.js scene, camera, renderer, and render loop. ```jsx import { Canvas } from '@react-three/fiber' function App() { return ( {/* 3D content goes here */} ) } ``` **Canvas Props**: - `camera` - Camera configuration (position, fov, near, far) - `gl` - WebGL renderer settings - `dpr` - Device pixel ratio (default: [1, 2]) - `shadows` - Enable shadow mapping (default: false) - `frameloop` - "always" (default), "demand", or "never" - `flat` - Disable color management for simpler colors - `linear` - Use linear color space instead of sRGB ### 2. Declarative 3D Objects Three.js objects are created using JSX with kebab-case props: ```jsx // THREE.Mesh + THREE.BoxGeometry + THREE.MeshStandardMaterial ``` **Prop Mapping**: - `position` → `object.position.set(x, y, z)` - `rotation` → `object.rotation.set(x, y, z)` - `scale` → `object.scale.set(x, y, z)` - `args` → Constructor arguments for geometry/material - `attach` → Attach to parent property (e.g., `attach="material"`) **Shorthand Notation**: ```jsx // Full notation // Axis-specific (dash notation) ``` ### 3. useFrame Hook Execute code on every frame (animation loop): ```jsx import { useFrame } from '@react-three/fiber' import { useRef } from 'react' function RotatingBox() { const meshRef = useRef() useFrame((state, delta) => { // Rotate mesh on every frame meshRef.current.rotation.x += delta meshRef.current.rotation.y += delta * 0.5 // Access scene state const time = state.clock.elapsedTime meshRef.current.position.y = Math.sin(time) * 2 }) return ( ) } ``` **useFrame Parameters**: - `state` - Scene state (camera, scene, gl, clock, etc.) - `delta` - Time since last frame (for frame-rate independence) - `xrFrame` - XR frame data (for VR/AR) **Important**: Never use `setState` inside `useFrame` - it causes unnecessary re-renders! ### 4. useThree Hook Access scene state and methods: ```jsx import { useThree } from '@react-three/fiber' function CameraInfo() { const { camera, gl, scene, size, viewport } = useThree() // Selective subscription (only re-render on size change) const size = useThree((state) => state.size) // Get state non-reactively const get = useThree((state) => state.get) const freshState = get() // Latest state without triggering re-render return null } ``` **Available State**: - `camera` - Default camera - `scene` - Three.js scene - `gl` - WebGL renderer - `size` - Canvas dimensions - `viewport` - Viewport dimensions in 3D units - `clock` - Three.js clock - `pointer` - Normalized mouse coordinates - `invalidate()` - Manually trigger render - `setSize()` - Manually resize canvas ### 5. useLoader Hook Load assets with automatic caching and Suspense integration: ```jsx import { Suspense } from 'react' import { useLoader } from '@react-three/fiber' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' import { TextureLoader } from 'three' function Model() { const gltf = useLoader(GLTFLoader, '/model.glb') return } function TexturedMesh() { const texture = useLoader(TextureLoader, '/texture.jpg') return ( ) } function App() { return ( }> ) } ``` **Loading Multiple Assets**: ```jsx const [texture1, texture2, texture3] = useLoader(TextureLoader, [ '/tex1.jpg', '/tex2.jpg', '/tex3.jpg' ]) ``` **Loader Extensions**: ```jsx import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader' useLoader(GLTFLoader, '/model.glb', (loader) => { const dracoLoader = new DRACOLoader() dracoLoader.setDecoderPath('/draco/') loader.setDRACOLoader(dracoLoader) }) ``` **Pre-loading**: ```jsx // Pre-load assets before component mounts useLoader.preload(GLTFLoader, '/model.glb') ``` --- ## Common Patterns ### Pattern 1: Basic Scene Setup ```jsx import { Canvas } from '@react-three/fiber' function Scene() { return ( <> {/* Lights */} {/* Objects */} ) } function App() { return ( ) } ``` ### Pattern 2: Interactive Objects (Click, Hover) ```jsx import { useState } from 'react' function InteractiveBox() { const [hovered, setHovered] = useState(false) const [active, setActive] = useState(false) return ( setActive(!active)} onPointerOver={() => setHovered(true)} onPointerOut={() => setHovered(false)} > ) } ``` ### Pattern 3: Animated Component with useFrame ```jsx import { useRef } from 'react' import { useFrame } from '@react-three/fiber' function AnimatedSphere() { const meshRef = useRef() useFrame((state, delta) => { // Rotate meshRef.current.rotation.y += delta // Oscillate position const time = state.clock.elapsedTime meshRef.current.position.y = Math.sin(time) * 2 }) return ( ) } ``` ### Pattern 4: Loading GLTF Models ```jsx import { Suspense } from 'react' import { useLoader } from '@react-three/fiber' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' function Model({ url }) { const gltf = useLoader(GLTFLoader, url) return ( ) } function App() { return ( }> ) } function LoadingPlaceholder() { return ( ) } ``` ### Pattern 5: Multiple Lights ```jsx function Lighting() { return ( <> {/* Ambient light for base illumination */} {/* Directional light with shadows */} {/* Point light for accent */} {/* Spot light for focused illumination */} ) } ``` ### Pattern 6: Instancing (Many Objects) ```jsx import { useMemo, useRef } from 'react' import * as THREE from 'three' import { useFrame } from '@react-three/fiber' function Particles({ count = 1000 }) { const meshRef = useRef() // Generate random positions const particles = useMemo(() => { const temp = [] for (let i = 0; i < count; i++) { const t = Math.random() * 100 const factor = 20 + Math.random() * 100 const speed = 0.01 + Math.random() / 200 const x = Math.random() * 2 - 1 const y = Math.random() * 2 - 1 const z = Math.random() * 2 - 1 temp.push({ t, factor, speed, x, y, z, mx: 0, my: 0 }) } return temp }, [count]) const dummy = useMemo(() => new THREE.Object3D(), []) useFrame(() => { particles.forEach((particle, i) => { let { t, factor, speed, x, y, z } = particle t = particle.t += speed / 2 const a = Math.cos(t) + Math.sin(t * 1) / 10 const b = Math.sin(t) + Math.cos(t * 2) / 10 const s = Math.cos(t) dummy.position.set( x + Math.cos((t / 10) * factor) + (Math.sin(t * 1) * factor) / 10, y + Math.sin((t / 10) * factor) + (Math.cos(t * 2) * factor) / 10, z + Math.cos((t / 10) * factor) + (Math.sin(t * 3) * factor) / 10 ) dummy.scale.set(s, s, s) dummy.updateMatrix() meshRef.current.setMatrixAt(i, dummy.matrix) }) meshRef.current.instanceMatrix.needsUpdate = true }) return ( ) } ``` ### Pattern 7: Groups and Nesting ```jsx function Robot() { return ( {/* Body */} {/* Head */} {/* Arms */} ) } ``` --- ## Integration with Drei Helpers [Drei](https://github.com/pmndrs/drei) is the essential helper library for R3F, providing ready-to-use components: ### OrbitControls ```jsx import { OrbitControls } from '@react-three/drei' ``` ### Environment & Lighting ```jsx import { Environment, ContactShadows } from '@react-three/drei' {/* HDRI environment map */} {/* Or custom */} {/* Soft contact shadows */} ``` ### Text ```jsx import { Text, Text3D } from '@react-three/drei' // 2D Billboard text Hello World // 3D extruded text 3D Text ``` ### useGLTF Hook (Drei) ```jsx import { useGLTF } from '@react-three/drei' function Model() { const { scene, materials, nodes } = useGLTF('/model.glb') return } // Pre-load useGLTF.preload('/model.glb') ``` ### Center & Bounds ```jsx import { Center, Bounds, useBounds } from '@react-three/drei' // Auto-center objects
// Auto-fit camera to bounds ``` ### HTML Overlay ```jsx import { Html } from '@react-three/drei'
This is a box
``` ### Scroll Controls ```jsx import { ScrollControls, Scroll, useScroll } from '@react-three/drei' import { useFrame } from '@react-three/fiber' function AnimatedScene() { const scroll = useScroll() const meshRef = useRef() useFrame(() => { const offset = scroll.offset // 0-1 normalized scroll position meshRef.current.position.y = offset * 10 }) return ... } {/* HTML overlay */}

Scrollable content

``` --- ## Integration with Other Libraries ### With GSAP ```jsx import { useRef, useEffect } from 'react' import { useFrame } from '@react-three/fiber' import gsap from 'gsap' function AnimatedBox() { const meshRef = useRef() useEffect(() => { // GSAP timeline animation const tl = gsap.timeline({ repeat: -1, yoyo: true }) tl.to(meshRef.current.position, { y: 2, duration: 1, ease: 'power2.inOut' }) .to(meshRef.current.rotation, { y: Math.PI * 2, duration: 2, ease: 'none' }, 0) return () => tl.kill() }, []) return ( ) } ``` ### With Framer Motion ```jsx import { motion } from 'framer-motion-3d' function AnimatedSphere() { return ( ) } ``` ### With Zustand (State Management) ```jsx import create from 'zustand' const useStore = create((set) => ({ color: 'orange', setColor: (color) => set({ color }) })) function Box() { const color = useStore((state) => state.color) const setColor = useStore((state) => state.setColor) return ( setColor('hotpink')}> ) } ``` --- ## Performance Optimization ### 1. On-Demand Rendering ```jsx {/* Only renders when needed */} // Manually trigger render function MyComponent() { const invalidate = useThree((state) => state.invalidate) return ( invalidate()}> ) } ``` ### 2. Instancing Use `` for rendering many identical objects: ```jsx function Particles({ count = 10000 }) { const meshRef = useRef() useEffect(() => { const temp = new THREE.Object3D() for (let i = 0; i < count; i++) { temp.position.set( Math.random() * 10 - 5, Math.random() * 10 - 5, Math.random() * 10 - 5 ) temp.updateMatrix() meshRef.current.setMatrixAt(i, temp.matrix) } meshRef.current.instanceMatrix.needsUpdate = true }, [count]) return ( ) } ``` ### 3. Frustum Culling Objects outside the camera view are automatically culled. ```jsx // Disable for always-visible objects ``` ### 4. LOD (Level of Detail) ```jsx import { Detailed } from '@react-three/drei' {/* High detail - close to camera */} {/* Medium detail */} {/* Low detail - far from camera */} ``` ### 5. Adaptive Performance ```jsx import { AdaptiveDpr, AdaptiveEvents, PerformanceMonitor } from '@react-three/drei' {/* Reduce DPR when performance drops */} {/* Reduce raycast frequency */} {/* Monitor and respond to performance */} console.log('Performance improved')} onDecline={() => console.log('Performance degraded')} > ``` ### 6. Selective Re-renders Use `useThree` selectors to avoid unnecessary re-renders: ```jsx // ❌ Re-renders on any state change const state = useThree() // ✅ Only re-renders when size changes const size = useThree((state) => state.size) // ✅ Only re-renders when camera changes const camera = useThree((state) => state.camera) ``` --- ## Common Pitfalls & Solutions ### ❌ Pitfall 1: setState in useFrame ```jsx // ❌ BAD: Triggers React re-renders every frame const [x, setX] = useState(0) useFrame(() => setX((x) => x + 0.1)) return ``` ✅ **Solution**: Mutate refs directly ```jsx // ✅ GOOD: Direct mutation, no re-renders const meshRef = useRef() useFrame((state, delta) => { meshRef.current.position.x += delta }) return ``` ### ❌ Pitfall 2: Creating Objects in Render ```jsx // ❌ BAD: Creates new Vector3 every render ``` ✅ **Solution**: Use arrays or useMemo ```jsx // ✅ GOOD: Use array notation // Or useMemo for complex objects const position = useMemo(() => new THREE.Vector3(1, 2, 3), []) ``` ### ❌ Pitfall 3: Not Using useLoader Cache ```jsx // ❌ BAD: Loads texture every render function Component() { const [texture, setTexture] = useState() useEffect(() => { new TextureLoader().load('/texture.jpg', setTexture) }, []) return texture ? : null } ``` ✅ **Solution**: Use useLoader (automatic caching) ```jsx // ✅ GOOD: Cached and reused function Component() { const texture = useLoader(TextureLoader, '/texture.jpg') return } ``` ### ❌ Pitfall 4: Conditional Mounting (Expensive) ```jsx // ❌ BAD: Unmounts and remounts (expensive) {stage === 1 && } {stage === 2 && } {stage === 3 && } ``` ✅ **Solution**: Use visibility prop ```jsx // ✅ GOOD: Components stay mounted, just hidden function Stage1({ visible, ...props }) { return ... } ``` ### ❌ Pitfall 5: useThree Outside Canvas ```jsx // ❌ BAD: Crashes - useThree must be inside Canvas function App() { const { size } = useThree() return ... } ``` ✅ **Solution**: Use hooks inside Canvas children ```jsx // ✅ GOOD: useThree inside Canvas child function CameraInfo() { const { size } = useThree() return null } function App() { return ( ) } ``` ### ❌ Pitfall 6: Not Disposing Resources ```jsx // ❌ BAD: Memory leak - textures not disposed const texture = useLoader(TextureLoader, '/texture.jpg') ``` ✅ **Solution**: R3F handles disposal automatically, but be careful with manual Three.js objects ```jsx // ✅ GOOD: Manual cleanup when needed useEffect(() => { const geometry = new THREE.SphereGeometry(1) const material = new THREE.MeshBasicMaterial() return () => { geometry.dispose() material.dispose() } }, []) ``` --- ## Best Practices ### 1. Component Composition Break scenes into reusable components: ```jsx function Lights() { return ( <> ) } function Scene() { return ( <> ) } ``` ### 2. Suspend Heavy Assets Always wrap async operations in Suspense: ```jsx }> ``` ### 3. Use TypeScript ```typescript import { ThreeElements } from '@react-three/fiber' function Box(props: ThreeElements['mesh']) { return ( ) } ``` ### 4. Organize by Feature ``` src/ components/ 3d/ Scene.tsx Lights.tsx Camera.tsx models/ Robot.tsx Character.tsx effects/ PostProcessing.tsx ``` ### 5. Test with React DevTools Profiler Monitor re-renders and optimize components causing performance issues. --- ## Resources ### References - `references/api_reference.md` - Complete R3F & Drei API documentation - `references/hooks_guide.md` - Detailed hooks usage and patterns - `references/drei_helpers.md` - Comprehensive Drei library guide ### Scripts - `scripts/component_generator.py` - Generate R3F component boilerplate - `scripts/scene_setup.py` - Initialize R3F scene with common patterns ### Assets - `assets/starter_r3f/` - Complete R3F + Vite starter template - `assets/examples/` - Real-world R3F component examples ### External Resources - [Official Docs](https://docs.pmnd.rs/react-three-fiber) - [Drei Docs](https://github.com/pmndrs/drei) - [Three.js Docs](https://threejs.org/docs/) - [R3F Discord](https://discord.gg/ZZjjNvJ) - [Poimandres (pmnd.rs)](https://pmnd.rs/) - Ecosystem overview