| name | react-three-fiber |
| description | Build 3D applications with React Three Fiber (R3F), the React renderer for Three.js. Use this skill when building 3D scenes with React, using declarative JSX for 3D objects, integrating Three.js with React state/lifecycle, or using Drei helpers. Covers Canvas setup, hooks, Drei utilities, performance patterns, and state management for 3D React apps. |
React Three Fiber
React Three Fiber (R3F) is a React renderer for Three.js. Write declarative, component-based 3D scenes using JSX.
Library Versions (2026)
- React Three Fiber: v9.5+
- @react-three/drei: v9.116+
- @react-three/rapier: v2+
- @react-three/postprocessing: v3+
- React: 19+ (concurrent features supported)
Decision Frameworks
When to Use R3F vs Vanilla Three.js
Is your app already React-based?
→ Yes: Use R3F (natural integration)
→ No: Consider vanilla Three.js
Do you need React state management?
→ Yes: Use R3F (seamless state integration)
→ No: Either works
Is the 3D scene the entire app?
→ Yes: Either works (R3F has slight overhead)
→ No, mixed with React UI: Use R3F
Performance-critical with millions of objects?
→ Consider vanilla Three.js for maximum control
→ R3F is fine for 99% of use cases
When to Use Which State Management
Local component state (single mesh color, hover)?
→ useState / useRef
Shared between few components (selected object)?
→ React Context or prop drilling
Global game/app state (score, inventory, settings)?
→ Zustand (recommended for R3F)
Complex state with actions/reducers?
→ Zustand with slices or Redux Toolkit
Need state persistence?
→ Zustand with persist middleware
When to Use Which Drei Component
Need camera controls?
├─ Orbit around object → OrbitControls
├─ First-person → PointerLockControls
├─ Map/top-down → MapControls
└─ Smooth transitions → CameraControls
Need environment lighting?
├─ Quick preset → <Environment preset="sunset" />
├─ Custom HDR → <Environment files="/env.hdr" />
└─ Performance-sensitive → <Environment blur={0.5} />
Need text?
├─ 3D text in scene → <Text3D />
├─ 2D text billboards → <Text />
└─ HTML overlay → <Html />
Need loading?
├─ GLTF models → useGLTF
├─ Textures → useTexture
├─ Progress UI → useProgress
└─ Preloading → <Preload all />
Core Setup
import { Canvas } from '@react-three/fiber'
function App() {
return (
<Canvas
camera={{ position: [0, 0, 5], fov: 75 }}
gl={{ antialias: true }}
shadows
>
<ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 5]} castShadow />
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
</Canvas>
)
}
Canvas Props
<Canvas
camera={{ position, fov, near, far }}
gl={{ antialias, alpha, powerPreference }}
shadows
dpr={[1, 2]}
frameloop="demand"
style={{ width: '100%', height: '100vh' }}
onCreated={({ gl, scene, camera }) => {}}
/>
Essential Hooks
useFrame - Animation Loop
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function SpinningBox() {
const meshRef = useRef()
useFrame((state, delta) => {
meshRef.current.rotation.y += delta
meshRef.current.position.y = Math.sin(state.clock.elapsedTime)
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}
useThree - Access Internals
import { useThree } from '@react-three/fiber'
function CameraLogger() {
const { camera, gl, scene, size, viewport, pointer } = useThree()
return null
}
useLoader - Asset Loading
import { useLoader } from '@react-three/fiber'
import { TextureLoader } from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
function Model() {
const gltf = useLoader(GLTFLoader, '/model.glb')
const texture = useLoader(TextureLoader, '/texture.jpg')
return <primitive object={gltf.scene} />
}
JSX to Three.js Mapping
<mesh position={[0, 1, 0]} rotation={[0, Math.PI, 0]} scale={1.5}>
{}
<boxGeometry args={[1, 2, 1]} />
{}
<meshStandardMaterial color="red" roughness={0.5} />
</mesh>
<directionalLight
position={[5, 5, 5]}
castShadow
shadow-mapSize={[2048, 2048]}
shadow-camera-far={50}
/>
<mesh>
<boxGeometry />
<meshStandardMaterial>
<texture attach="map" image={img} />
</meshStandardMaterial>
</mesh>
Drei - Essential Helpers
Drei provides production-ready abstractions. Install: @react-three/drei
import {
OrbitControls,
PerspectiveCamera,
Environment,
useGLTF,
useTexture,
Text,
Html,
Float,
Stage,
ContactShadows,
Sky,
Stars,
} from '@react-three/drei'
Common Drei Components
<OrbitControls enableDamping dampingFactor={0.05} />
<Environment preset="sunset" background />
const { scene, nodes, materials } = useGLTF('/model.glb')
useGLTF.preload('/model.glb')
const [colorMap, normalMap] = useTexture(['/color.jpg', '/normal.jpg'])
<Text fontSize={1} color="white" anchorX="center" anchorY="middle">
Hello World
</Text>
<Html position={[0, 2, 0]} center transform occlude>
<div className="label">Click me</div>
</Html>
<Float speed={2} rotationIntensity={0.5} floatIntensity={1}>
<mesh>...</mesh>
</Float>
<Stage environment="city" intensity={0.5}>
<Model />
</Stage>
<ContactShadows position={[0, -0.5, 0]} opacity={0.5} blur={2} />
Event Handling
<mesh
onClick={(e) => {
e.stopPropagation()
console.log('clicked', e.point, e.face)
}}
onPointerOver={(e) => setHovered(true)}
onPointerOut={(e) => setHovered(false)}
onPointerMove={(e) => console.log(e.point)}
onPointerDown={(e) => {}}
onPointerUp={(e) => {}}
onDoubleClick={(e) => {}}
onContextMenu={(e) => {}}
>
Event object includes: point, face, faceIndex, distance, object, eventObject, camera, ray.
State Management
With Zustand (Recommended)
import { create } from 'zustand'
const useStore = create((set) => ({
score: 0,
gameState: 'idle',
addScore: (points) => set((state) => ({ score: state.score + points })),
startGame: () => set({ gameState: 'playing' }),
}))
function ScoreDisplay() {
const score = useStore((state) => state.score)
return <Text>{score}</Text>
}
function GameLogic() {
const addScore = useStore((state) => state.addScore)
useFrame(() => {
})
return null
}
With React Context
const GameContext = createContext()
function GameProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<GameContext.Provider value={{ state, dispatch }}>
{children}
</GameContext.Provider>
)
}
<Canvas>
<GameProvider>
<Scene />
</GameProvider>
</Canvas>
Performance Patterns
Instancing
import { Instances, Instance } from '@react-three/drei'
function Boxes({ count = 1000 }) {
return (
<Instances limit={count}>
<boxGeometry />
<meshStandardMaterial />
{Array.from({ length: count }, (_, i) => (
<Instance
key={i}
position={[Math.random() * 100, Math.random() * 100, Math.random() * 100]}
color={`hsl(${Math.random() * 360}, 50%, 50%)`}
/>
))}
</Instances>
)
}
Selective Rendering
<Canvas frameloop="demand">
{}
</Canvas>
function Controls() {
const { invalidate } = useThree()
return <OrbitControls onChange={() => invalidate()} />
}
Memoization
const ExpensiveModel = memo(function ExpensiveModel({ url }) {
const gltf = useGLTF(url)
return <primitive object={gltf.scene.clone()} />
})
const geometry = new THREE.BoxGeometry(1, 1, 1)
const material = new THREE.MeshStandardMaterial({ color: 'red' })
function Box() {
return (
<mesh geometry={geometry} material={material} />
)
}
Adaptive Performance
import { PerformanceMonitor, AdaptiveDpr, AdaptiveEvents } from '@react-three/drei'
<Canvas>
<PerformanceMonitor
onDecline={() => setQuality('low')}
onIncline={() => setQuality('high')}
>
<AdaptiveDpr pixelated />
<AdaptiveEvents />
<Scene quality={quality} />
</PerformanceMonitor>
</Canvas>
Related Skills
| When you need... | Use skill |
|---|
| Vanilla Three.js (no React) | → threejs |
| Optimize assets before loading | → asset-pipeline-3d |
| Debug visual/performance issues | → graphics-troubleshooting |
Reference Files