0.6.0-preview6
Pre-releaseThis pre-release version includes:
- Upgraded to Elmish v4
- Completely reworked
useElmish
hook
Upgraded to Elmish v4 (Breaking)
1) Breaking Changes to UI Thread Sync
Program.withSyncDispatch
has been removed in Elmish v4 because the platform specificProgram.runWith___
functions should instead be provided by the library authors.
Additional info
the purpose of the "sync dispatch" is to ensure that UI changes coming from non-UI threads can be properly synchronized via the underlying platform specific dispatcher to avoid race conditions.
Previously, FuncUI automatically configured this within the Program.withHost
function, on behalf of the user, to guard against race conditions that could occur if they called dispatch
from an async
handler.
Avalonia.FuncUI upgrade path
A new Program.runWithAvaloniaSyncDispatch
function has been added that conforms to the new expectation that this should be handled by the library. If you are calling dispatch
from within async
blocks, you will need to use this instead of Program.run
. Otherwise, you will get exceptions, which will force you to use this new function.
Old Elmish v3 Sample with async blocks
Program.mkProgram init update view
|> Program.withHost this
|> Program.run
New Elmish v4 Sample with async blocks
Program.mkProgram init update view
|> Program.withHost this
|> Program.runWithAvaloniaSyncDispatch ()
2) Breaking Changes to Subscriptions
Cmd.ofSub
has been removedProgram.withSubscription
now takes in the state/model, and returns a list of named subscriptions.- Subscription functions must now return
IDisposable
so they can be automatically disposed
The above changes will cause breakage in any FuncUI apps that make use of Program.withSubscription
.
Avalonia.FuncUI upgrade path
Any FuncUI app that was previously using Program.withSubscription
will have to resolve a build error since the function inputs/outputs have changed.
Old Elmish v3 Sample with Multiple Subscriptions
let timer (_state: Clock.State) =
let sub (dispatch: Clock.Msg -> unit) =
let invoke() =
DateTime.Now |> Clock.Msg.Tick |> dispatch
true
DispatcherTimer.Run(Func<bool>(invoke), TimeSpan.FromMilliseconds 1000.0) |> ignore
Cmd.ofSub sub
// Save state when host window is Closed
let onClosedSub (state: Clock.State) =
Cmd.ofSub (fun _ ->
this.Closed.Subscribe(fun e ->
printfn "Saving state: %A" state
)
|> ignore
)
Program.mkSimple (fun () -> Clock.init) Clock.update Clock.view
|> Program.withHost this
|> Program.withSubscription timer
|> Program.withSubscription onClosedSub
|> Program.withConsoleTrace
|> Program.run
New Elmish v4 Sample with Multiple Subscriptions
let subscriptions (_state: Clock.State) : Sub<Clock.Msg> =
let timerSub (dispatch: Clock.Msg -> unit) =
let invoke() =
DateTime.Now |> Clock.Msg.Tick |> dispatch
true
DispatcherTimer.Run(Func<bool>(invoke), TimeSpan.FromMilliseconds 1000.0) // <- IDisposable
let onClosedSub (dispatch: Clock.Msg -> unit) =
this.Closed.Subscribe(fun e -> printfn "The window has been closed.")
// Notes:
// - Each subscription has a unique identifier
// - Subscriptions can be conditionally add/removed from the list based on the passed in model
// - Each subscription returns IDisposable, and is automatically disposed if conditionally removed from the list
[
[ nameof timerSub ], timerSub
[ nameof onClosedSub ], onClosedSub
]
Program.mkSimple Clock.init Clock.update Clock.view
|> Program.withHost this
|> Program.withSubscription subscriptions
|> Program.withConsoleTrace
|> Program.run
Completely reworked useElmish hook (Breaking)
useElmish
now actually uses Elmish 4 under the hood! 🎉
This means that useElmish
can now take advantage of all the great Elmish features like subscriptions and async commands!
It has two main variations of overloads:
- Two that take
init
,update
,initArg
and an optionalmapProgram
(for adding subsriptions or other Elmish extensions) - Two that take
writableModel
,update
and an optionalmapProgram
(for adding subsriptions or other Elmish extensions)
Here is an example from the ChordParserView
sample that uses subscriptions:
let private subscriptions (model: Model) : Sub<Msg> =
let timerSub (dispatch: Msg -> unit) =
let invoke() = dispatch Msg.SetTime; true
DispatcherTimer.Run(invoke, TimeSpan.FromMilliseconds 1000.0)
[
// Dynamically start or stop (Dispose) subscription
if model.Transpose = 0 then
[ nameof timerSub ], timerSub
]
let view () = Component (fun ctx ->
let model, dispatch = ctx.useElmish(init, update, (), Program.withSubscription subscriptions)
//let model, dispatch = ctx.useElmish(init, update, ()) // if no subscriptions are needed
Grid.create [
Thanks to @alfonsogcnunez for paving the way as I relied heavily on his Fable.React.UseElmish implementation!