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, // container

View on GitHub (pinned to ff3899dbf4)

Solutions

  1. Ensure the code that creates the root has a matching cleanup (unmount) in the effect
  2. Remove accidental duplicate <Canvas> elements sharing the same canvas DOM node
  3. In StrictMode, ensure you're on an @react-three/fiber version compatible with React 18/19 double-mounting semantics
  4. 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

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.