Skip to content

vdom

wybthon.vdom

Virtual DOM primitives, diffing, and rendering to real DOM elements.

This module re-exports the public API from the focused sub-modules:

  • :mod:wybthon.vnode -- VNode, h, Fragment, memo
  • :mod:wybthon.reconciler -- render, mount, unmount, patch
  • :mod:wybthon.error_boundary -- ErrorBoundary
  • :mod:wybthon.suspense -- Suspense
  • :mod:wybthon.portal -- create_portal
  • :mod:wybthon.props -- prop-diffing helpers (is_event_prop, etc.)

All names previously importable from wybthon.vdom continue to work.

VNode dataclass

Virtual node representing an element, text, or component subtree.

ErrorBoundary(props)

Catch render errors in children and display a fallback.

Props
  • fallback: VNode | str | callable(error, reset) -> VNode
  • on_error: optional callback invoked with the caught exception
  • reset_key / reset_keys: when this value changes the error is auto-cleared
  • children: child VNodes to render when there is no error

Fragment(*args)

Group multiple children without adding a visible wrapper to the DOM.

Uses a <span style="display:contents"> so the wrapper is invisible to CSS layout while keeping the VDOM diffing algorithm simple.

Can be called directly::

Fragment(child1, child2)

Or used as a component tag via h()::

h(Fragment, {}, child1, child2)

Suspense(props)

Render a fallback while one or more resources are loading.

Props
  • resources | resource: Resource or list of Resources
  • fallback: VNode | str | callable returning VNode/str
  • keep_previous: bool (default False)
  • children: child VNodes to render when not loading

create_portal(children, container)

Render children into a different DOM container.

Returns a VNode that, when mounted, renders children into container instead of the parent component's DOM node. Useful for modals, tooltips, and overlays that need to break out of their parent's DOM hierarchy.

container may be an Element or a CSS selector string.

h(tag, props=None, *children)

Create a VNode from a tag, props, and children (component-aware).

is_event_prop(name)

Return True if a prop name is an event handler prop like on_click or onClick.

memo(component, are_props_equal=None)

Memoize a function component to skip re-renders when props are unchanged.

By default uses shallow identity comparison (is) on each prop value. Pass a custom are_props_equal(old_props, new_props) -> bool for deeper comparison logic.

mount(vnode, container, anchor=None)

Mount a VNode (or string) into the container, returning its element.

patch(old, new, container)

Patch old into new by mutating DOM as needed within the container.

render(vnode, container)

Render a VNode tree into a container Element or CSS selector.

unmount(vnode)

Unmount a VNode and dispose associated resources and effects.

The VDOM system is implemented as a set of focused sub-modules. The wybthon.vdom module re-exports all public names for convenience:

Module Responsibility
wybthon.vnode VNode, h(), Fragment, memo()
wybthon.reconciler render(), mount(), unmount(), patch()
wybthon.props DOM prop application, style/event/dataset diffing
wybthon.error_boundary ErrorBoundary component
wybthon.suspense Suspense component
wybthon.portal create_portal()

You can import from either wybthon.vdom or the specific sub-module:

from wybthon.vdom import h, render          # re-export hub
from wybthon.vnode import h                 # direct import
from wybthon.reconciler import render       # direct import

Public API

  • VNode
  • h(tag, props=None, *children) -> VNode
  • render(vnode, container) -> Element
  • ErrorBoundary component
  • Suspense component
  • memo(component, are_props_equal=None) — memoize a function component
  • create_portal(children, container) — render children into a different DOM container

Keyed children and diffing

  • h(tag, {"key": key}, ...) assigns a stable identity to a child.
  • During reconciliation, children are matched by key first, then by type for unkeyed nodes.
  • Reorders are applied with minimal DOM moves using a right-to-left pass with a moving anchor.
  • Unmatched old nodes are unmounted; unmatched new nodes are mounted at the correct anchor.

Text nodes (fast-path)

  • When both the old and new nodes are text, the same DOM text node is reused and only nodeValue is updated.
  • Lists of plain text children are reconciled efficiently; typically the framework updates text in-place and minimizes DOM moves.
  • Example:
from wybthon import h, render
from wybthon.dom import Element

root = Element(node=document.createElement("div"))
render(h("div", {}, "hello"), root)
render(h("div", {}, "world"), root)  # updates the same text node

Suspense

Suspense renders a fallback while one or more resources are loading.

  • Props:
  • resource or resources=[...]
  • fallback – VNode/str/callable
  • keep_previous=False – keep children visible during subsequent reloads

ErrorBoundary

ErrorBoundary catches render errors from its subtree and renders a fallback.

  • Props:
  • fallback – VNode/str/callable. When callable, it is invoked as fallback(error, reset); if the callable only accepts one argument, it is invoked as fallback(error).
  • on_error – optional callback called with the thrown error when the boundary captures it.
  • reset_key – any value; when this value changes, the boundary automatically resets (clears the error) on the next render.
  • reset_keys – list/tuple of values; when the tuple of values changes, the boundary automatically resets.

  • Methods:

  • reset() – imperative method to clear the current error and attempt re-rendering children.

  • Notes:

  • If the fallback callable throws, a simple text node "Error rendering fallback" is shown.
  • When not in an error state, the boundary renders its children wrapped in a Fragment.

Prop semantics (style, dataset, value, checked)

  • style: pass a dict of CSS properties using camelCase keys. Keys are converted to kebab-case and applied via style.setProperty. On updates, keys that are absent in the new dict are removed with style.removeProperty. Passing None or a non-dict clears previously set style keys.

python h("div", {"style": {"backgroundColor": "red", "fontSize": 14}}) # → sets background-color: red; font-size: 14

  • dataset: pass a dict; entries map to data-* attributes. On updates, keys not present are removed. Passing None or a non-dict clears previously set data-* attributes.

python h("div", {"dataset": {"id": "x", "role": "button"}}) # → sets data-id="x" data-role="button"

  • value: for form controls, the DOM value property is set (falling back to the value attribute if needed). None becomes "". Removing the value prop resets it to "".

  • checked: for checkboxes/radios, the DOM checked property is set when available (falling back to the checked attribute). Removing the checked prop clears it to False.

memo

memo(component, are_props_equal=None) wraps a function component to skip re-renders when props are unchanged.

  • By default, uses shallow identity comparison (is) on each prop value.
  • Pass a custom are_props_equal(old_props, new_props) -> bool for deeper comparison.
from wybthon import memo

def ExpensiveList(props):
    # ... render a large list ...
    pass

MemoList = memo(ExpensiveList)

create_portal

create_portal(children, container) renders children into a different DOM container.

  • children: a single VNode or a list of VNodes.
  • container: an Element or CSS selector string.
from wybthon import create_portal, h

portal = create_portal(h("div", {}, "Modal content"), "#modal-root")

Development mode

Wybthon includes a development mode (DEV_MODE = True by default) that provides clear error messages to stderr when something goes wrong during rendering, event handling, or lifecycle hooks. Errors include component names and tracebacks.

from wybthon import set_dev_mode

set_dev_mode(False)  # disable for production