Lifecycle Hooks

Mount, update, unmount, error boundaries, and browser window events — all auto-cleaned on unmount.

Components register lifecycle handlers in their init block via the lifecycle DSL. Each hook can be registered multiple times; all handlers fire in registration order.

Subscribing to the same hook multiple times is totally fine. Handlers don't replace each other — they stack. When the event fires, each registered handler is called one after the other in the order it was registered. This lets you split unrelated concerns into separate blocks (e.g. one onMount for analytics, another for initializing a third-party widget) without having to merge them into a single handler.
init {
    lifecycle {
        onMount {
            // First handler — runs first
            analytics.trackView("dashboard")
        }
        onMount {
            // Second handler — runs after the first
            tooltip = Tooltip(dom!!)
        }
    }
}
class MyComponent(ctx: Ctx<Props>) : Component<Props>(ctx) {
    init {
        lifecycle {
            onMount { /* ... */ }
            onUpdate { /* ... */ }
            onUnmount { /* ... */ }
            onNextProps { new, old -> /* ... */ }
            onError { e -> /* ... */ }
            onResize { entries -> /* ... */ }
            onWindowResize { v -> /* ... */ }
            onWindowFocus { /* ... */ }
            onWindowBlur { /* ... */ }
        }
    }
}
Stream subscriptions (via subscribingTo()) auto-unsubscribe when the component unmounts. Window and resize hooks registered through the lifecycle DSL also clean themselves up. You rarely need to track unsubscribe functions manually.

Core hooks

onMount

Fires after the component's DOM has been rendered. The dom property now points at the actual HTMLElement.

init {
    lifecycle {
        onMount {
            // dom is available here
            console.log("Mounted at", dom?.tagName)

            // Attach third-party widgets that need a live element
            val tooltip = Tooltip(dom!!)
        }
    }
}

onUpdate

Fires after every re-render (state change, props change, parent redraw). Use this when you need to run code after the DOM has been updated — for example, scrolling a container into view or syncing a third-party widget.

init {
    lifecycle {
        onUpdate {
            // Component re-rendered; DOM is in sync with state
            console.log("child count", dom?.children?.length)
        }
    }
}

onUnmount

Fires when the component is removed from the DOM. Clean up anything the lifecycle doesn't handle for you (third-party widgets, manually-registered listeners).

private var tooltip: Tooltip? = null

init {
    lifecycle {
        onMount { tooltip = Tooltip(dom!!) }
        onUnmount { tooltip?.destroy(); tooltip = null }
    }
}

onNextProps

Fires when a parent passes new props. Receives both the new and old props, so you can react to specific changes.

init {
    lifecycle {
        onNextProps { new, old ->
            if (new.query != old.query) {
                reload()
            }
        }
    }
}

onError — error boundaries

Errors thrown during render or by child components bubble up the component tree, looking for an onError handler. The nearest ancestor with an onError catches the error; if none do, the error propagates to Preact's default handling.

class Dashboard(ctx: NoProps) : PureComponent(ctx) {
    private var error: Throwable? by value(null)

    init {
        lifecycle {
            onError { e ->
                // Log, report to analytics, and show a fallback
                console.error("Caught", e.message)
                error = e
            }
        }
    }

    override fun VDom.render() {
        val err = error
        if (err != null) {
            ui.negative.message {
                +"Something went wrong: ${err.message}"
                ui.button {
                    onClick { error = null }
                    +"Retry"
                }
            }
            return
        }

        // Any error thrown inside these children bubbles up to onError above
        WidgetGrid()
        SalesChart()
        ActivityFeed()
    }
}
Bubbling stops at the first component with an onError hook. If you want errors to bubble past a component that already has a handler, don't catch them there — handle them higher up.

onResize — element resize (ResizeObserver)

Wraps the browser's ResizeObserver API and observes this component's DOM element. The observer is attached on mount and disconnected on unmount automatically.

class ResponsiveCanvas(ctx: NoProps) : PureComponent(ctx) {
    private var width: Double by value(0.0)
    private var height: Double by value(0.0)

    init {
        lifecycle {
            onResize { entries ->
                val box = entries[0].contentRect
                width = box.width
                height = box.height
            }
        }
    }

    override fun VDom.render() {
        div {
            css {
                this.width = 100.pct
                this.height = 100.pct
            }
            +"${width.toInt()} x ${height.toInt()}"
        }
    }
}

Useful for canvas games, responsive charts, or anything that needs to adapt when its container changes size — fires even when the element is resized by a sibling layout change, not just window resize.

onWindowResize / onWindowFocus / onWindowBlur — browser window events

For events on the browser window, use these hooks. They dispatch through the app-level WindowController (registered by default in every kraftApp ) and auto-unsubscribe on unmount.

class WindowTracker(ctx: NoProps) : PureComponent(ctx) {
    private var size: Vector2D? by value(null)
    private var focused: Boolean by value(true)

    init {
        lifecycle {
            onWindowResize { v: Vector2D ->
                // v.x = window.innerWidth, v.y = window.innerHeight
                size = v
            }

            onWindowFocus {
                focused = true
            }

            onWindowBlur {
                focused = false
            }
        }
    }

    override fun VDom.render() {
        div {
            +"window: ${size?.x?.toInt()} x ${size?.y?.toInt()}"
            +" | focused: $focused"
        }
    }
}
When to use which? Use onResize for layout-aware components that need to react to their own element changing size. Use onWindowResize for global concerns like viewport-based layout or responsive breakpoints.

Reference — all hooks

Hook Signature Fires when
onMount () -> Unit Component inserted into the DOM
onUpdate () -> Unit Component re-rendered (DOM updated)
onUnmount () -> Unit Component removed from the DOM
onNextProps (new, old) -> Unit Parent passes new props
onError (Throwable) -> Unit Error thrown in render or child component
onResize (Array<ResizeObserverEntry>) -> Unit Component's DOM element resized
onWindowResize (Vector2D) -> Unit Browser window resized
onWindowFocus () -> Unit Browser window gains focus
onWindowBlur () -> Unit Browser window loses focus