pmndrs/react-three-fiber · warning
RTTR: attempted to update an unmounted root!
Error message
RTTR: attempted to update an unmounted root!
What it means
The test-renderer's root.update() checks the internal mockRoots registry; if the canvas key is absent the root was already unmounted, so it warns 'RTTR: attempted to update an unmounted root!' and skips the render instead of acting on a dead root.
Source
Thrown at packages/test-renderer/src/index.tsx:61
scene: wrapFiber(_scene),
async unmount() {
await act(async () => {
_root.unmount()
})
},
getInstance() {
// Bail if canvas is unmounted
if (!mockRoots.has(canvas)) return null
// Traverse fiber nodes for R3F root
const root = { current: mockRoots.get(canvas)!.fiber.current }
while (!root.current.child?.stateNode) root.current = root.current.child
// Return R3F instance from root
return reconciler.getPublicRootInstance(root)
},
async update(newElement: React.ReactNode) {
if (!mockRoots.has(canvas)) return console.warn('RTTR: attempted to update an unmounted root!')
await act(async () => {
_root.render(newElement)
})
},
toTree() {
return toTree(_scene)
},
toGraph() {
return toGraph(_scene)
},
fireEvent: createEventFirer(act, _store),
async advanceFrames(frames: number, delta: number | number[] = 1) {
const state = _store.getState()
const storeSubscribers = state.internal.subscribers
const promises: Promise<void>[] = []
View on GitHub (pinned to ff3899dbf4)
Solutions
- Check the root's mounted state (or recreate via createRoot) before calling update/advance
- Move advance()/update calls before unmount/afterEach teardown
- Ensure beforeEach/afterEach correctly create and dispose rttr roots so cached references aren't reused
- Await all pending act() promises before unmounting so no tick fires afterwards
Example fix
// before const root = createRTTRRoot(canvas) root.render(<Scene />) root.unmount() root.update(<Scene v2 />) // warns // after const root = createRTTRRoot(canvas) root.render(<Scene />) root.update(<Scene v2 />) root.unmount()
Defensive patterns
Strategy: type-guard
Validate before calling
let disposed = false
const safeUpdate = (el: React.ReactNode) => {
if (disposed) return
root.update(el)
} Type guard
const isMounted = (root: { unmount: () => void } & Record<string, any>): boolean =>
mockRoots.has(root.canvas) // or track a local `disposed` flag set in unmount wrapper Prevention
- Wrap unmount to set a disposed flag and check it before update/advance
- Create fresh roots per test in beforeEach; dispose in afterEach
- Await all act() promises before teardown so no late tick renders a dead root
When it happens
Trigger: Keeping a reference to an rttr root after calling root.unmount() (or after the test finished/tore down) and then calling root.update(element) or advance()/loop() ticks that render.
Common situations: Test helpers that cache roots across tests without resetting, awaiting act() after unmount, or calling advance() in a fake-timer loop after the root was cleaned up in afterEach.
Related errors
- `${prefix} ${msg}`
- Handler for ${eventName} was not found. You must pass event
- R3F.createRoot should only be called once!
AI-assisted analysis of pmndrs/react-three-fiber@ff3899dbf4 (2026-08-28).
Data as JSON: /api/errors/963ce4c83b317a6e.
Report an issue: GitHub.