Lifecycle feedback and page state
Prerequisites
Start with a LiveView and a root layout that renders liveTitle. Review
Lifecycle and connection behavior
before attaching effects to lifecycle hooks.
Render Keyed Flash
Define a stable FlashKindopaque type FlashKind = StringA nominal key for one category of flash message., update it through the phase context, and render only
that key with flashobject flash:
private val Saved = FlashKind("saved")
case Msg.Save =>
save *> ctx.flash.put(Saved, "Changes saved.").as(model)
def view(model: Signal[Model]) =
div(
flash(Saved) { message =>
p(role := "status", aria.live := "polite", message)
}
)putdef put(kind: FlashKind, message: String): zio.package.Task[Unit] replaces the value for the same key.
cleardef clear(kind: FlashKind): zio.package.Task[Unit] removes that key, and
clearAlldef clearAll: zio.package.Task[Unit] removes every flash value owned by the current lifecycle. Prefer a
specific key when independent notices can coexist.
Flash is lifecycle state, not a substitute for model data. Use it for brief feedback such as a save result or navigation notice. Keep values free of secrets, and put information that must survive arbitrary reloads in durable application state instead.
Derive The Page Title
Override pageTitledef pageTitle(model: Model): Option[String] on the routed root
LiveViewtrait LiveView[Msg, Model]Defines a server-rendered view with typed messages and model state. and derive it from the same
model exposed as a signal to viewdef view(model: Signal[Model]): HtmlElement[Msg]:
override def pageTitle(model: Model): Option[String] =
Some(model.currentTitle)The root layout's liveTitledef liveTitle(pageTitle: Option[String], default: String, prefix: String = ..., suffix: String = ...): HtmlElement[Nothing] renders that title during disconnected HTTP
rendering. Connected model changes send title metadata so the client updates
document.title. Returning None or a blank title uses the root layout's
fallback.
Only the root LiveViewtrait LiveView[Msg, Model]Defines a server-rendered view with typed messages and model state. owns the document title. A nested
LiveViewtrait LiveView[Msg, Model]Defines a server-rendered view with typed messages and model state. can project a
title-like value for its own interface, but its pageTitledef pageTitle(model: Model): Option[String] result does not
replace the containing document's title. The embedded lifecycle example makes
that boundary visible by displaying its projection inside the example.
Show Connection State
Render both states and let declarative connection bindings switch them in the browser:
div(
span(connection.visibleWhenConnected, "Connected"),
span(connection.visibleWhenDisconnected, "Offline")
)The disconnected state is visible in static HTML by default. Once the LiveSocket connects, the client hides it and reveals the connected state; it reverses those attributes if the transport drops. Include text or another non-color cue so the state remains understandable without color perception.
Controls that need the server should not imply that an offline click succeeded. Scalive's documentation shell freezes embedded examples while disconnected; applications can use the same connection bindings with JS commands to disable or explain unavailable interaction.
Keep After-Render Effects Observational
Declare static hooks once on the LiveViewtrait LiveView[Msg, Model]Defines a server-rendered view with typed messages and model state.:
override def hooks: LiveHooks[Msg, Model] =
LiveHooks.afterRender { (model, ctx) =>
ctx.connection match
case Connection.Connected(_) => recordRenderedTitle(model.currentTitle)
case Connection.Disconnected => ZIO.unit
}An after-render hook observes a render that already succeeded. It cannot return
a replacement model. Use handleMessagedef handleMessage(model: Model, ctx: MessageContext): Msg => zio.package.Task[Model]Handles a message against the current immutable model.,
handleParamsdef handleParams(model: Model, params: Params, url: zio.http.URL, ctx: ParamsContext): zio.package.Task[Model], an async completion,
or a subscription message when an effect must produce the next state.
Hooks are installed independently for disconnected and connected lifecycles.
Match ctx.connectiondef connection: Connection[Connected],
keep effects idempotent where practical, and avoid starting unmanaged fibers
from a hook.
Exercise The Behavior
Use the lifecycle example to put and clear flash,
change the projected title, inspect connected mount state, and reset the nested
LiveViewtrait LiveView[Msg, Model]Defines a server-rendered view with typed messages and model state.. Its source keeps the model, messages, flash key, title projection, and
after-render hook together.
For the full lifecycle sequence and reconnect model, read Lifecycle, state ownership, and reconnects.
Related Tasks
Install the title-owning document shell with Layouts, live sessions, and mount aspects.
Deliver delayed UI state through Asynchronous work and subscriptions.
Verify title and reconnect behavior with Testing LiveViews.