pmndrs/react-three-fiber · warning
R3F.createRoot should only be called once!
Error message
R3F.createRoot should only be called once!
What it means
R3F keeps a registry of roots per canvas element; calling createRoot on a canvas that already has a root warns 'R3F.createRoot should only be called once!' because the previous root's state (scene, store, subscriptions) would be orphaned. It is a console warning, not a throw, but usually indicates a leak or double-mount.
Source
Thrown at packages/fiber/src/core/renderer.tsx:150
width: canvas.width,
height: canvas.height,
top: 0,
left: 0,
}
}
return { width: 0, height: 0, top: 0, left: 0, ...size }
}
export function createRoot<TCanvas extends HTMLCanvasElement | OffscreenCanvas>(
canvas: TCanvas,
): ReconcilerRoot<TCanvas> {
// Check against mistaken use of createRoot
const prevRoot = _roots.get(canvas)
const prevFiber = prevRoot?.fiber
const prevStore = prevRoot?.store
if (prevRoot) console.warn('R3F.createRoot should only be called once!')
// Report when an error was detected in a previous render
// https://github.com/pmndrs/react-three-fiber/pull/2261
const logRecoverableError =
typeof reportError === 'function'
? // In modern browsers, reportError will dispatch an error event,
// emulating an uncaught JavaScript error.
reportError
: // In older browsers and test environments, fallback to console.error.
console.error
// Create store
const store = prevStore || createStore(invalidate, advance)
// Create renderer
const fiber =
prevFiber ||
(reconciler as any).createContainer(
store, // containerView on GitHub (pinned to ff3899dbf4)
Solutions
- Ensure the code that creates the root has a matching cleanup (unmount) in the effect
- Remove accidental duplicate <Canvas> elements sharing the same canvas DOM node
- In StrictMode, ensure you're on an @react-three/fiber version compatible with React 18/19 double-mounting semantics
- Key the canvas node or reuse the existing root via _roots instead of re-calling createRoot
Example fix
// before
useEffect(() => {
const root = createRoot(canvasEl)
root.render(<Scene />)
})
// after
useEffect(() => {
const root = createRoot(canvasEl)
root.render(<Scene />)
return () => root.unmount()
}, []) Defensive patterns
Strategy: validation
Validate before calling
import { _roots } from '@react-three/fiber' // or track roots yourself
const existing = trackedRoots.get(canvasEl)
if (existing) { existing.render(<Scene />) } else { trackedRoots.set(canvasEl, createRoot(canvasEl)) } Type guard
const hasExistingRoot = (canvas: HTMLCanvasElement): boolean => trackedRoots.has(canvas)
Prevention
- Always return a cleanup that unmounts the root from effects
- Use a single <Canvas> per canvas DOM node; don't manually createRoot over it
- Test with StrictMode locally to catch double-mount leaks early
When it happens
Trigger: Calling createRoot(canvas) twice on the same <canvas> DOM node — typically from StrictMode double-effect in React 18/19, or from a component whose effect creates/destroys the root without cleanup, or manually re-initializing the renderer.
Common situations: React.StrictMode in development double-invoking effects around <Canvas>, HMR remounting the canvas component, an effect missing its cleanup function, or version upgrades to React 18+ where effects run twice in dev.
Related errors
AI-assisted analysis of pmndrs/react-three-fiber@ff3899dbf4 (2026-08-28).
Data as JSON: /api/errors/50140079d7a0fa8a.
Report an issue: GitHub.