Lifecycle and Cleanup
This page describes Regor lifecycle and teardown behavior from the actual runtime flow.
Runtime lifecycle order
Section titled “Runtime lifecycle order”createApp(...) runs in this order:
- Resolve root (
element,selector, or default#appfor string template shortcut). - If
template/jsonis provided, replace root content first. - Run interpolation (when enabled in config).
- Bind directives/components.
- Register root cleanup callback.
- Call mounted lifecycle (
onMounted(...)callbacks and optionalcontext.mounted()method).
Hook registration with useScope
Section titled “Hook registration with useScope”onMounted / onUnmounted are scope-based APIs.
In practice, register them while creating context inside useScope(...):
import { createApp, html, onMounted, onUnmounted, ref, useScope } from 'regor'
class AppCtx { count = ref(0) logs: string[] = []
constructor() { onMounted(() => this.logs.push('mounted')) onUnmounted(() => this.logs.push('unmounted')) }}
const app = createApp( useScope(() => new AppCtx()), { element: document.querySelector('#app')!, template: html`<p r-text="count"></p>`, },)Calling these hooks outside an active scope throws (ComposablesRequireScope).
unmount() vs unbind()
Section titled “unmount() vs unbind()”Returned app exposes both:
app.unmount()- Removes root node from DOM (
removeNode(root)). - Unbind runs through deferred queue.
- Removes root node from DOM (
app.unbind()- Unbinds root subtree immediately.
- Keeps DOM in place.
Use unmount() when app is gone.
Use unbind() when markup stays but Regor behavior must stop.
Deferred cleanup queue and drainUnbind()
Section titled “Deferred cleanup queue and drainUnbind()”removeNode(...) queues unbind work and flushes it with a short timer.
This keeps removal fast, but cleanup side effects are not always immediate in the same tick.
drainUnbind() forces pending queue flush now.
Typical test teardown:
import { createApp, drainUnbind, html, ref } from 'regor'
const root = document.createElement('div')const app = createApp( { n: ref(1) }, { element: root, template: html`<p r-text="n"></p>` },)
app.unmount()await drainUnbind()Component lifecycle notes
Section titled “Component lifecycle notes”Component teardown is registered through unbinders on component markers/host nodes. When component subtree is unbound:
onUnmounted(...)callbacks run for component context.context.unmounted?.()runs if defined.- Directive observers/listeners are detached.
- Slot-switch helper contexts are released.
ComponentHead.unmount()
Section titled “ComponentHead.unmount()”Inside component context, head.unmount() is a force-remove tool:
- Removes nodes between component start/end markers.
- Calls unmounted lifecycle for captured component contexts.
Use only when component wants to self-remove its rendered block.
Practical cleanup patterns
Section titled “Practical cleanup patterns”- Page-level app:
Prefer
app.unmount()on route/page disposal. - Keep DOM, disable behavior:
Use
app.unbind(). - Test suites:
app.unmount()+await drainUnbind()infinally. - Manual reactive resources: If you create observers/effects outside scoped/component lifecycle, stop them explicitly.