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.