Skip to main content

Page Module

The Page module provides functions for creating and configuring pages in your Elmish Land application.

Where is this defined?​

The Page module is automatically generated during the build process:

  • Generated Location: .elmish-land/Base/Page.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/Page.fs file is auto-generated and should not be edited directly. Any changes will be overwritten on the next build.

Page.from​

Creates a page with init, update, and view functions.

Signature​

Page.from :
init: (unit -> 'model * Command<'msg, 'sharedMsg, 'layoutMsg>)
-> update: ('msg -> 'model -> 'model * Command<'msg, 'sharedMsg, 'layoutMsg>)
-> view: ('model -> ('msg -> unit) -> ReactElement)
-> props: 'props
-> layoutMsgCtor: ('layoutMsg -> 'msg)
-> Page<'model, 'msg, 'sharedMsg, 'layoutMsg, 'props>

Parameters​

  • init - Function that returns the initial model and optional command when the page first loads
  • update - Function that handles messages and returns updated model and optional commands
  • view - Function that renders the model to React elements
  • props - Initial properties to pass to the page (typically unit)
  • layoutMsgCtor - Constructor for wrapping layout messages (e.g., LayoutMsg)

Example​

type Model = { Count: int }

type Msg =
| Increment
| Decrement
| LayoutMsg of Layout.Msg

let init () =
{ Count = 0 }, Command.none

let update msg model =
match msg with
| Increment -> { model with Count = model.Count + 1 }, Command.none
| Decrement -> { model with Count = model.Count - 1 }, Command.none
| LayoutMsg _ -> model, Command.none

let view model dispatch =
Html.div [
Html.button [
prop.onClick (fun _ -> dispatch Decrement)
prop.text "-"
]
Html.span (string model.Count)
Html.button [
prop.onClick (fun _ -> dispatch Increment)
prop.text "+"
]
]

let page (shared: SharedModel) (route: HomeRoute) =
Page.from init update view () LayoutMsg

Page.withSubscriptions​

Adds subscriptions to a page for handling external events like timers or WebSocket messages.

Signature​

Page.withSubscriptions :
subscriptions: ('model -> (string list * ('model -> Dispatch<'msg> -> IDisposable)) list)
-> page: Page<'model, 'msg, 'sharedMsg, 'layoutMsg, 'props>
-> Page<'model, 'msg, 'sharedMsg, 'layoutMsg, 'props>

Parameters​

  • subscriptions - Function that returns a list of subscriptions. Each subscription consists of:
    • A unique ID (string list) for the subscription
    • A function that sets up the subscription and returns an IDisposable for cleanup

Example​

type Model = { Count: int }

type Msg =
| Tick
| LayoutMsg of Layout.Msg

let init () =
{ Count = 0 }, Command.none

let update msg model =
match msg with
| Tick -> { model with Count = model.Count + 1 }, Command.none
| LayoutMsg _ -> model, Command.none

let view model _dispatch =
Html.div [
Html.span (string model.Count)
]

let onEverySecond model dispatch =
let intervalId = Browser.Dom.window.setInterval(
(fun () -> dispatch Tick),
1000
)
React.createDisposable (fun () ->
Browser.Dom.window.clearInterval(intervalId)
)

let subscriptions model =
[
[ "timer" ], onEverySecond model
]

let page (shared: SharedModel) (route: HomeRoute) =
Page.from init update view () LayoutMsg
|> Page.withSubscriptions subscriptions

See the Subscriptions guide for detailed examples and patterns.

Best Practices​

Keep Pages Focused​

Each page should be responsible for a single route and its specific functionality. Share common logic through the Shared module, layouts or helper functions.

Use Type-Safe Routing​

Leverage the generated route types to access URL parameters and query strings in a type-safe manner:

let page (shared: SharedModel) (route: UsersIdRoute) =
// route.Id is type-safe and guaranteed to exist
let init () =
{ UserId = route.Id }, Command.none

Page.from init update view () LayoutMsg

Initialize with Commands​

Use the init function to trigger side effects when the page loads:

let init () =
{ Users = []; Loading = true },
Command.ofPromise
fetchUsers
()
UsersLoaded
LoadError

Handle Layout Messages​

Always include a case in your message type for layout messages, even if you don't use them:

type Msg =
| MyMsg
| LayoutMsg of Layout.Msg

let update msg model =
match msg with
| MyMsg -> model, Command.none
| LayoutMsg _ -> model, Command.none

This ensures your page can receive messages from its layout.