Navigate
Search topics across all sections
useThree
Sometimes you need more than just animating a mesh. You need the camera. The renderer. The viewport size. The mouse position. The entire internal state of your 3D scene. useThree is the hook that gives you the keys to everything.
You call useThree() with no arguments to grab the camera. It works, but now your component re-renders on every mouse movement, every window resize, every single frame tick. Your FPS drops and you can't figure out why.
React DevTools: "MyComponent" rendered 847 times in 10 seconds. Profiler shows useThree() causing re-renders on every pointer move. Cause: subscribing to entire state store without a selector.
Real-world
Imagine walking into a film studio's control room. There are monitors showing every camera angle, buttons for every light, dials for resolution and frame rate, and a readout of mouse position relative to the set.
useThree gives you access to this control room. You can read any monitor, press any button, twist any dial. But you do not want a notification every time any dial twitches -- you want to subscribe to only the monitors you care about. That is what selectors do.
What Lives in the State
gl
The WebGL renderer
scene
The root scene object
camera
Active camera
viewport
Visible area in 3D units
pointer
Normalized mouse position
clock
Elapsed time + delta
See It In Action
This demo uses useThree to read viewport dimensions and adapt the scene layout in real time. Try resizing your browser window.
Building It Step by Step
Step 1 -- Select only what you need
Always pass a selector function. This tells R3F to only re-render your component when that specific value changes -- not when anything in the entire state store changes.
const camera = useThree((s) => s.camera)
const viewport = useThree((s) => s.viewport)
const gl = useThree((s) => s.gl)Each selector creates a targeted subscription. Your component stays quiet until its specific value changes.
Step 2 -- Use viewport for 3D layout
viewport.width and viewport.height tell you how much of the 3D world is visible at the camera's distance. Use these to position objects edge-to-edge.
function EdgeToEdgePlane() {
const { width, height } = useThree((s) => s.viewport)
return (
<mesh>
<planeGeometry args={[width, height]} />
<meshBasicMaterial color="#1a1a2e" />
</mesh>
)
}The plane now fills the entire camera view perfectly, regardless of window size or aspect ratio.
Step 3 -- Use size for responsive breakpoints
size gives you the canvas dimensions in CSS pixels. Use it for mobile vs. desktop layout decisions, not for positioning 3D objects.
function ResponsiveScene() {
const size = useThree((s) => s.size)
const isMobile = size.width < 768
return (
<mesh position={[isMobile ? 0 : -2, 0, 0]}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
)
}size = pixels (1920x1080). viewport = Three.js units (~11x6). Use the right one for the right job.
What you just learned
useThree gives you access to the entire R3F internal state (renderer, camera, scene, etc.)
Always pass a selector to avoid unnecessary re-renders
viewport gives 3D world units -- use it for positioning 3D objects
size gives CSS pixels -- use it for responsive breakpoints
invalidate() only works with frameloop='demand' for on-demand rendering
Question
Can you use useThree outside of a Canvas? No. useThree accesses an internal context that only exists inside the Canvas component tree. If you need R3F state in a regular React component outside Canvas, use R3F's createRoot API and manage the connection yourself.
Think about it...
You want to make a 3D plane that perfectly fills the screen. Which value do you use for its width and height?
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Toggle showCorners off -- where do the corner objects go?
Try This!
Beginner
Set centerScale to 3 -- giant center object!
Try This!
Intermediate
Resize your browser window -- watch the viewport values update.
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
function MyComponent() {
// Subscribes to the ENTIRE state object
const state = useThree()
const { camera } = state
// Re-renders when ANYTHING changes!
}const { size } = useThree()
// size.width = 1920 (pixels!)
// This plane is 1920 Three.js units wide!
<planeGeometry args={[size.width, size.height]} />// Default frameloop="always"
<Canvas>
<Scene />
</Canvas>
function Scene() {
const invalidate = useThree((s) => s.invalidate)
// This does nothing -- canvas already renders every frame
invalidate()
}Best Practices
Always use selectors
useThree(s => s.camera) prevents re-renders from unrelated state changes. Never call useThree() bare.
viewport for 3D, size for 2D
Use viewport (Three.js units) for 3D positioning. Use size (pixels) for responsive breakpoints.
Demand mode for static scenes
Use frameloop="demand" with invalidate() for scenes that only change on user interaction. Saves GPU and battery.
Use set() sparingly
state.set() overrides R3F internals. Only use it when you need a custom camera or raycaster. The defaults work for most cases.