Skip to content
scalive
Menu
ConnectingLiveReconnectingOffline

Lifecycle and connection state

Compare the connected mount with declarative LiveSocket state, put and clear one keyed notification, and change the title projected from the model. The example runs as an isolated nested LiveView, so it displays its projected title without claiming ownership of the documentation page's browser title.

LiveView
scala
final class LifecycleExample extends LiveView[LifecycleExample.Msg, LifecycleExample.Model]:
  import LifecycleExample.*

  override def hooks: LiveHooks[Msg, Model] =
    LiveHooks.empty[Msg, Model].afterRender { (model, ctx) =>
      ZIO
        .when(ctx.connection.isInstanceOf[Connection.Connected[?]]) {
          ZIO.logDebug(s"Lifecycle example rendered with title '${model.currentTitle}'")
        }.unit
    }

  override def pageTitle(model: Model): Option[String] = Some(model.currentTitle)

  def mount(ctx: MountContext): Task[Model] =
    ZIO.succeed(
      Model(
        connectedMount = ctx.connection.isInstanceOf[Connection.Connected[?]],
        currentTitle = DefaultTitle
      )
    )

  def handleMessage(model: Model, ctx: MessageContext) =
    case Msg.PutNotification =>
      ctx.flash.put(NotificationFlash, "Your notification is ready.").as(model)
    case Msg.ClearNotification =>
      ctx.flash.clear(NotificationFlash).as(model)
    case Msg.RequestAttention =>
      ZIO.succeed(model.copy(currentTitle = AttentionTitle))
    case Msg.Reset =>
      ctx.flash.clear(NotificationFlash).as(model.copy(currentTitle = DefaultTitle))

  override def view(model: Signal[Model]): HtmlElement[Msg] =
    div(
      cls := "docs-lifecycle",
      div(
        cls := "docs-lifecycle-state",
        sectionTag(
          p(cls := "docs-lifecycle-label", "Mount phase"),
          strong(
            dataAttr("mount-phase") := "",
            model.map(model =>
              if model.connectedMount then "Connected socket mount" else "Disconnected HTTP mount"
            )
          )
        ),
        sectionTag(
          p(cls                              := "docs-lifecycle-label", "Projected page title"),
          strong(dataAttr("lifecycle-title") := "", model.map(_.currentTitle))
        )
      ),
      div(
        cls := "docs-lifecycle-connection",
        span(connection.visibleWhenConnected, "LiveSocket connected"),
        span(connection.visibleWhenDisconnected, "LiveSocket disconnected")
      ),
      flash(NotificationFlash) { message =>
        p(
          dataAttr("lifecycle-flash") := "",
          cls                         := "docs-lifecycle-flash",
          role                        := "status",
          message
        )
      },
      fieldSet(
        dataAttr("example-controls") := "",
        legend(cls := "docs-visually-hidden", "Lifecycle controls"),
        button(typ := "button", on.click(Msg.PutNotification), "Show notification"),
        button(typ := "button", on.click(Msg.ClearNotification), "Clear notification"),
        button(typ := "button", on.click(Msg.RequestAttention), "Request attention"),
        button(typ := "button", on.click(Msg.Reset), "Reset example")
      )
    )
end LifecycleExample

object LifecycleExample:
  final case class Model(connectedMount: Boolean, currentTitle: String)

  enum Msg:
    case PutNotification
    case ClearNotification
    case RequestAttention
    case Reset

  private val NotificationFlash = FlashKind("notification")
  private val DefaultTitle      = "Lifecycle example"
  private val AttentionTitle    = "Attention needed"

Result

ConnectedReconnectingRead-only

Mount phase

Disconnected HTTP mount

Projected page title

Lifecycle example
LiveSocket disconnected
Lifecycle controls

Disconnected. Controls resume after reconnection.

UnavailableConnect to capture interactions

Related guidance: follow the complete lifecycle or apply flash, title, and connection UX.