a reactive (signals based) hypermedia web framework (wip)
stormlightlabs.github.io/volt/
hypermedia
frontend
signals
1# Lifecycle Hooks
2
3Volt's runtime exposes lifecycle hooks so you can observe mounts, run cleanup logic, and coordinate plugins without re-implementing binding internals. Hooks run consistently for both SSR hydration and client-only mounts.
4
5## Lifecycle Layers
6
7- **Global hooks** fire for every mount/unmount operation and are ideal for analytics, logging, or cross-cutting concerns.
8- **Element hooks** attach to a single DOM element and let you react to that element entering or leaving the document.
9- **Plugin hooks** are available while authoring custom bindings and let you scope mount/unmount work to a plugin instance.
10
11## Global Hooks
12
13Register global hooks with `registerGlobalHook(name, callback)`. The available events are:
14
15| Event | Position |
16| -------------------------- | ---------------------------------------------------------------------------------------------------- |
17| `beforeMount(root, scope)` | Runs right before bindings initialize |
18| | This is the place to patch the scope or read serialized state |
19| `afterMount(root, scope)` | Runs after VoltX has attached bindings and lifecycle state |
20| `beforeUnmount(root)` | Runs before a root is torn down, giving you time to flush pending work |
21| `afterUnmount(root)` | Runs after cleanup finishes |
22| | Use this to release global resources |
23
24```ts
25import { registerGlobalHook } from "@volt/volt";
26
27const unregister = registerGlobalHook("afterMount", (root, scope) => {
28 console.debug("[volt] mounted", root.id, scope);
29});
30
31unregister();
32```
33
34### Working with the Scope Object
35
36`beforeMount` and `afterMount` receive the reactive scope for the root element so you can read signal values or stash helpers on the scope.
37Avoid mutating DOM inside these hooks-leave DOM updates to bindings/plugins to prevent hydration mismatches.
38
39### Managing Global Hooks
40
41- Use `unregisterGlobalHook` when the callback is no longer needed.
42- Call `clearGlobalHooks("beforeMount")` or `clearAllGlobalHooks()` in test teardown code to avoid cross-test leakage.
43- Prefer one central module to register global hooks so they are easy to audit.
44
45## Element Hooks
46
47When you need per-element notifications, register element hooks:
48
49```ts
50import { registerElementHook, isElementMounted } from "@volt/volt";
51
52const panel = document.querySelector("[data-volt-panel]");
53
54registerElementHook(panel!, "mount", () => {
55 console.log("panel is live");
56});
57
58registerElementHook(panel!, "unmount", () => {
59 console.log("panel removed, dispose timers");
60});
61
62if (isElementMounted(panel!)) {
63 // Safe to touch DOM or read bindings immediately.
64}
65```
66
67Element hooks automatically dispose after the element unmounts. Use `getElementBindings(element)` when debugging to see which binding directives are attached to a node.
68
69## Plugin Lifecycle Hooks
70
71Custom plugins receive lifecycle helpers on the plugin context:
72
73```ts
74import type { PluginContext } from "@volt/volt";
75
76export function focusPlugin(ctx: PluginContext) {
77 const el = ctx.element as HTMLElement;
78
79 ctx.lifecycle.onMount(() => el.focus());
80 ctx.lifecycle.onUnmount(() => el.blur());
81}
82```
83
84- `ctx.lifecycle.onMount` and `ctx.lifecycle.onUnmount` let you coordinate DOM state with the binding's lifetime.
85- Use `ctx.lifecycle.beforeBinding` and `ctx.lifecycle.afterBinding` to measure binding creation or guard against duplicate initialization.
86- Always combine lifecycle hooks with `ctx.addCleanup` if you create subscriptions that outlive a single mount cycle.
87
88## Best Practices
89
90- Keep hook callbacks side-effect free whenever possible; defer heavy work to asynchronous tasks.
91- Never mutate the DOM tree that VoltX currently manages from `beforeMount`; wait for `afterMount` or plugin hooks instead.
92- When adding analytics or telemetry, remember to remove hooks on navigation or single-page route changes to avoid duplicate events.
93- In tests, seed hooks inside the test body and tear them down with the disposer returned from `registerGlobalHook` to preserve isolation.
94
95For server-rendered workflows and hydration patterns, refer to [ssr](./ssr).