Lifecycle: onMount

onMount

Run code when a component is mounted to the DOM.

Understanding Mount in MoonBit

In Luna's architecture:

  • Server (MoonBit): Renders HTML strings, no DOM access

  • Client (TypeScript): Has DOM access, handles mount lifecycle

For server-side MoonBit code, "mount" happens when the HTML is generated. For actual DOM mount events, use client-side TypeScript.

Server-Side Initialization

On the server, you can initialize state during render:

using @server_dom { div, text }
using @luna { signal }

fn component_with_init() -> @luna.Node {
  // This runs during render (server-side "mount")
  let data = load_initial_data()
  let count = signal(data.initial_count)

  div([
    text("Initial: " + count.get().to_string())
  ])
}

Effect as Mount

Effects run immediately after creation, similar to mount:

using @luna { signal, effect }

fn component() -> @luna.Node {
  let status = signal("initializing")

  // This runs right after creation
  effect(fn() {
    println("Component initialized")
    status.set("ready")
  })

  div([text("Status: " + status.get())])
}

One-Time Setup

For one-time initialization, use a flag:

let initialized = signal(false)

fn setup_once() {
  if not(initialized.get()) {
    initialized.set(true)
    // One-time setup code
    println("Setup complete")
  }
}

Client-Side Mount

For actual DOM mount, the client-side TypeScript handles it:

// TypeScript (client-side)
import { onMount } from '@luna_ui/luna';

function Counter(props) {
  onMount(() => {
    console.log("Component mounted to DOM!");

    // DOM access available here
    document.title = "Counter";
  });

  return <button>Count</button>;
}

Passing Mount Data from Server

Serialize data for client-side mount:

using @server_dom { island }
using @luna { Load }

struct InitData {
  user_id : Int
  preferences : Preferences
} derive(ToJson)

fn user_component(data : InitData) -> @luna.Node {
  island(
    id="user",
    url="/static/user.js",
    state=data.to_json().stringify(),  // Client receives this on mount
    trigger=Load,
    children=[...],
  )
}

Common Patterns

Lazy Initialization

fn lazy_component() -> @luna.Node {
  let data = signal(None)

  // Simulate lazy loading
  effect(fn() {
    if data.get().is_none() {
      let loaded = fetch_data()
      data.set(Some(loaded))
    }
  })

  match data.get() {
    Some(d) => render_data(d)
    None => div([text("Loading...")])
  }
}

Derived Initial State

fn derived_component(items : Array[Item]) -> @luna.Node {
  // Calculate derived state at mount
  let total = items.fold(0, fn(sum, item) { sum + item.value })
  let average = total / items.length()

  div([
    p([text("Total: " + total.to_string())]),
    p([text("Average: " + average.to_string())]),
  ])
}

Server vs Client Lifecycle

EventServer (MoonBit)Client (TypeScript)
Component createdRender function runsComponent function runs
DOM attachedN/AonMount callback
Signal changedN/AEffects re-run
Component removedN/AonCleanup callback

Try It

Create a component that:

  1. Initializes with data from a parameter

  2. Logs when the effect first runs

  3. Passes initialization data to an island for client mount

Next

Learn about onCleanup โ†’