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 serverandelmish-land buildcommands - Namespace:
ElmishLand
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 loadsupdate- Function that handles messages and returns updated model and optional commandsview- Function that renders the model to React elementsprops- Initial properties to pass to the page (typicallyunit)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
IDisposablefor 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.