Skip to main content

Layout Module

The Layout module provides functions for creating and configuring layouts in Elmish Land. Layouts are UI components that wrap pages and preserve state during navigation.

Where is this defined?

The Layout module is automatically generated during the build process:

  • Generated Location: .elmish-land/Base/Layout.fs (in your project)
  • Generated By: Running elmish-land restore, elmish-land server and elmish-land build commands
  • Namespace: ElmishLand
warning

The .elmish-land/Base/Layout.fs file is auto-generated and should not be edited directly. Any changes will be overwritten on the next build.

Layout.from

Creates a layout with init, update, routeChanged, and view functions.

Signature

Layout.from :
init: (unit -> 'layoutModel * Command<'layoutMsg, 'sharedMsg, 'layoutMsg>)
-> update: ('layoutMsg -> 'layoutModel -> 'layoutModel * Command<'layoutMsg, 'sharedMsg, 'layoutMsg>)
-> routeChanged: ('layoutModel -> 'layoutModel * Command<'layoutMsg, 'sharedMsg, 'layoutMsg>)
-> view: ('layoutModel -> ReactElement -> ('layoutMsg -> unit) -> ReactElement)
-> Layout<'sharedMsg, 'layoutModel, 'layoutMsg>

Parameters

  • init - Initialization function that returns the initial model and command. Called once when the layout first loads.
  • update - Message handler that receives a message and current model, returning updated model and command.
  • routeChanged - Function called on every navigation except initial load. Receives current model, returns updated model and command.
  • view - Rendering function that receives the current model, page content to wrap, and dispatch function. Returns the layout's ReactElement.

Example

module MyProject.Pages.User.Layout

open Feliz
open ElmishLand
open MyProject.Shared

type Props = unit

type Model = {
IsMenuOpen: bool
}

type Msg =
| ToggleMenu
| CloseMenu

let init () =
{ IsMenuOpen = false },
Command.none

let update (msg: Msg) (model: Model) =
match msg with
| ToggleMenu ->
{ model with IsMenuOpen = not model.IsMenuOpen },
Command.none
| CloseMenu ->
{ model with IsMenuOpen = false },
Command.none

let routeChanged (model: Model) =
// Close menu on navigation
{ model with IsMenuOpen = false },
Command.none

let view (model: Model) (content: ReactElement) (dispatch: Msg -> unit) =
Html.div [
Html.nav [
Html.button [
prop.onClick (fun _ -> dispatch ToggleMenu)
prop.text "Menu"
]
if model.IsMenuOpen then
Html.div [
Html.text "User Menu Items"
]
]
Html.main [
content
]
]

let layout (_props: Props) (_route: Route) (_shared: SharedModel) =
Layout.from init update routeChanged view

Layout.withSubscriptions

Adds subscriptions to a layout, allowing it to listen to external events like timers or WebSocket connections.

Signature

Layout.withSubscriptions :
subscriptions: ('layoutModel -> (string list * (('layoutMsg -> unit) -> System.IDisposable)) list)
-> layout: Layout<'sharedMsg, 'layoutModel, 'layoutMsg>
-> Layout<'sharedMsg, 'layoutModel, 'layoutMsg>

Parameters

  • subscriptions - Function that receives the current model and returns a list of subscriptions. Each subscription is a tuple of (ID list, start function).
  • layout - The layout to add subscriptions to.

Example

let onNotificationReceived model dispatch =
// Setup WebSocket listener
let subscription () =
dispatch (NotificationReceived "New message")
let intervalId = Browser.Dom.window.setInterval(subscription, 5000)
React.createDisposable (fun () ->
Browser.Dom.window.clearInterval(intervalId)
)

let subscriptions model =
[
if model.IsAuthenticated then
[ "notifications" ], onNotificationReceived model
]

let layout (_props: Props) (_route: Route) (shared: SharedModel) =
Layout.from init update routeChanged view
|> Layout.withSubscriptions subscriptions

Best Practices

When to Use routeChanged vs init

  • init: Use for one-time setup when the layout first loads. The layout model persists across navigation, so init is only called once.
  • routeChanged: Use for actions that should occur on every navigation, like navigating between pages with the same layout.