useRenderQuality
A scene gate decides when a 3D scene should run and how good it should look, but React Three Fiber has no idea any of that happened. It keeps rendering off screen, at whatever pixel ratio the display reports, and it ships no handler for a lost context, so the first time the browser reclaims one the canvas goes black and stays black.
useRenderQuality turns a useSceneGate decision into props you spread onto <Canvas>. Call it outside the canvas, because that is where its output goes.
It does three things. Pixel ratio and shadows follow the scene's state, so a constrained scene draws cheaper without being rebuilt. An idle scene gets frameloop: "never", which stops the loop while keeping the context, its textures and its compiled shaders. And it attaches the context-loss listener React Three Fiber does not, so a dead canvas reports itself and the generation counter can rebuild it.
Since 4.1.0 it also finds the WebGPU device behind the renderer and watches that, so a WebGPU scene recovers the same way. Nothing about how you write the scene changes.
◆Live React Three Fiber Quality Governance
Quick start
Obtain scene state from useSceneGate, generate canvas props with useRenderQuality, and spread them onto <Canvas>.
Never pass conflicting dpr, shadows, or frameloop props to <Canvas> alongside {...canvasProps}.
What the adapter owns, and what your components own
The adapter sets renderer settings. How much there is to draw stays in your own components.
The adapter owns dpr, shadows, frameloop and the loss wiring. Those are properties of the renderer, they live outside the canvas, and it can genuinely apply them.
Instance counts, geometry detail and shader complexity are not renderer settings. They live inside your components, and the gate hands you quality to branch on. Collapsing the two is what produces a scene that is either always heavy or always poor, because the only dial left is resolution.
The props are returned rather than applied because React Three Fiber re-runs its configure pass on every render of <Canvas> and resets those properties from JSX props. A hook that set them imperatively was overwritten a frame later. That was the 4.0.0 change: a renderer asked for dpr: 1 was running at 1.25.
API Reference
Syntax
Converts a SceneGate state into declarative React Three Fiber <Canvas> configuration props.
Parameters
| Parameters.field | Type | Default | Description |
|---|---|---|---|
| state | SceneState | (none) | Active state returned by useSceneGate ('dormant', 'warming', 'active', 'constrained', 'idle', 'recovering', 'poster'). |
| profiles.full | RenderProfile ({ dpr?: number; shadows?: boolean }) | (none) | Renderer configuration applied when state is 'active'. From 4.2.0 its dpr is a ceiling: the canvas never draws above the screen's own ratio. |
| profiles.reduced | RenderProfile ({ dpr?: number; shadows?: boolean }) | (none) | Renderer configuration applied during 'constrained' or 'warming' states. |
Return Value
Props object designed to be spread onto <Canvas {...canvasProps}>.
Properties & State
| Properties.field | Type | Description |
|---|---|---|
| dpr | number | undefined | The active profile's pixel ratio, capped at the screen's own from 4.2.0: dpr: 2 draws a 1x monitor at 1x and a 3x phone at 2. Earlier versions applied it exactly as written. |
| frameloop | "always" | "never" | Set to 'never' when state is 'idle' to halt GPU render cycles. |
| shadows | boolean | undefined | Whether shadow map passes are enabled. |
| onCreated | (state) => void | Callback attaching context loss event listeners to the canvas DOM element. |
Context Loss Recovery & Unmount
The adapter handles canvas recreation and WebGL context restoration cleanly.
Lifecycle & Invariant Guarantees
- →An off-screen scene costs nothing: An idle scene gets frameloop: "never", so the loop stops while the context, its textures and its compiled shaders stay in memory.
- →Resolution holds while a scene is idle: Pixel ratio stops changing once drawing stops, so the last frame survives. Lowering it would resize the drawing buffer, which clears it, and nothing is left running to repaint. Needs 4.0.2 or newer.
- →A dead canvas reports itself: The listener calls preventDefault first, because some drivers refuse a replacement context otherwise, then reports the loss so the generation can move.
- →WebGPU recovers the same way: It also finds the device behind a WebGPU renderer and watches it, and keeps the WebGL listener attached, which is what covers three's silent fallback to WebGL. Needs 4.1.0 or newer.
- →Display DPR Clamping: Configured DPR profiles serve as upper ceilings clamped to window.devicePixelRatio, preventing excessive fragment shader overdraw on lower-density displays.
- →Lifecycle Teardown Isolation: React Three Fiber deliberately forces context loss when unmounting a canvas. The listener discriminates unmounting teardowns from abnormal context loss, preventing modal closures or route changes from triggering unwanted scene rebuild loops.
- →Mount-Phase Recovery Guard: Context losses that occur while React Three Fiber initializes the scene graph are captured during the onCreated phase, ensuring early initialization failures recover automatically instead of leaving canvases unrendered.
- →Spreading the props does not churn the canvas: The returned object is memoised, so a re-render that changes nothing does not reconfigure the renderer.
◆Production Examples
Battle-tested production patterns ready to copy directly into your codebase.
Integrate SceneGate with RenderQuality for an adaptive full-screen 3D hero.
When NOT to use this
Rules & Gotchas
- →Do not also pass dpr, shadows or frameloop to <Canvas>: React Three Fiber reconciles all three from props on every render, so the later prop wins and the adapter's decision is silently discarded. Quality levels belong in the profiles argument.
- →Put key={scene.generation} on <Canvas> itself: Keying an inner component leaves the dead context mounted, so recovery produces a second dead canvas.
- →Call it outside <Canvas>: It produces props that <Canvas> consumes. Called inside the tree it is asking for context that does not exist there yet.
- →Spread the whole object: Picking fields out of it means picking which of its four jobs still happen, and the one people drop is onCreated, which is the loss handling.