drainUnbind

Flush the pending unbind queue after nodes have been removed.

drainUnbindCleanup and Unbind

Flush the pending unbind queue after nodes have been removed.

Overview

drainUnbind flushes Regor's pending unbind queue immediately.

Regor defers some unbind work after removeNode(...) for better runtime behavior. Use drainUnbind() when you need deterministic cleanup timing.

Try it live

Own a node through its full lifetimeLive API

Source value: 0 · Node: absent

0Source observers
0Root unbinders
0Custom cleanup calls

Unbind keeps the node but freezes its bindings. Remove + drain removes it and flushes deferred cleanup.

import {
  addUnbinder,
  createApp,
  drainUnbind,
  getBindData,
  html,
  observerCount,
  onUnmounted,
  ref,
  removeNode,
  sref,
  unbind,
  useScope,
} from 'regor'

export function createCleanup(interactive = false) {
  const host = sref<HTMLElement | null>(null)
  const source = ref(0),
    value = ref(0),
    present = ref(false),
    bound = ref(false)
  const observers = ref(0),
    callbacks = ref(0),
    registered = ref(0)
  let node: HTMLElement | null = null
  const inspect = () => {
    observers(observerCount(source))
    registered(node ? getBindData(node).unbinders.length : 0)
  }
  const mount = () => {
    if (!host() || node) return
    node = document.createElement('div')
    node.className = 'directive-card api-owned-node'
    host()!.appendChild(node)
    createApp(
      useScope(() => ({ source })),
      {
        element: node,
        template:
          '<strong>Owned node</strong><p>Bound value: <output r-text="source"></output></p>',
      },
    )
    addUnbinder(node, () => callbacks(callbacks() + 1))
    present(true)
    bound(true)
    inspect()
  }
  const detach = () => {
    if (node) unbind(node)
    bound(false)
    inspect()
  }
  const remove = async () => {
    if (!node) return
    removeNode(node)
    await drainUnbind()
    node = null
    present(false)
    bound(false)
    inspect()
  }
  onUnmounted(() => {
    if (node) {
      unbind(node)
      node.remove()
    }
  })
  return {
    interactive,
    host,
    value,
    present,
    bound,
    observers,
    callbacks,
    registered,
    mount,
    detach,
    remove,
    increment: () => {
      source(source() + 1)
      value(source())
      inspect()
    },
  }
}

export const cleanupTemplate = html` <div class="guide-demo">
  <div class="guide-session-bar">
    <button type="button" @click="mount" :disabled="!interactive || present">
      Mount node</button
    ><button type="button" @click="increment" :disabled="!interactive">
      Write +1</button
    ><button type="button" @click="detach" :disabled="!interactive || !bound">
      Unbind</button
    ><button type="button" @click="remove" :disabled="!interactive || !present">
      Remove + drain
    </button>
  </div>
  <p>
    Source value: <strong>{{ value }}</strong> · Node: {{ present ? 'present' :
    'absent' }}
  </p>
  <div :ref="host"></div>
  <div class="guide-metrics">
    <div><output>{{ observers }}</output><span>Source observers</span></div>
    <div><output>{{ registered }}</output><span>Root unbinders</span></div>
    <div><output>{{ callbacks }}</output><span>Custom cleanup calls</span></div>
  </div>
  <p class="guide-hint">
    Unbind keeps the node but freezes its bindings. Remove + drain removes it
    and flushes deferred cleanup.
  </p>
</div>`
import { createApp, RegorConfig, useScope } from 'regor'
import { apiExamples } from './examples'

const element = document.getElementById('api-demo')
const name = element?.dataset.example
if (element && name && Object.hasOwn(apiExamples, name)) {
  const example = apiExamples[name as keyof typeof apiExamples]
  const scope = useScope<object>(() => example.create(true))
  const config =
    'config' in scope.context && scope.context.config instanceof RegorConfig
      ? scope.context.config
      : undefined
  createApp(scope, { element, template: example.template }, config)
}

Mount, write, unbind, or remove and drain. Watch the DOM, observer count, and registered cleanup.

Usage

import { createApp, drainUnbind, html, ref } from 'regor'

const root = document.createElement('div')
const show = ref(true)

const app = createApp({
  template: html`<div><span r-if="show">Hello</span></div>`,
  setup: { show },
})

app.mount(root)
show(false) // schedules node removal + deferred unbind

await drainUnbind() // force pending unbind cleanup now

Signature

drainUnbind(): Promise<void>
Use it when cleanup must be complete

Await drainUnbind in checks or integrations that depend on the deferred queue being empty.

Notes

  • Safe to call when there is nothing pending.
  • Useful in tests when you need cleanup completed before assertions.
  • Complements removeNode and unbind; it does not replace app lifecycle (app.unmount()).

See Also

Related APIs and guides Continue with the references connected to drainUnbind.

Back to the API list