Skip to content

0.6.0-preview6

Pre-release
Pre-release
Compare
Choose a tag to compare
@JordanMarr JordanMarr released this 22 Jan 21:18
· 224 commits to master since this release
7bd1ff0

This 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 specific Program.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 removed
  • Program.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 optional mapProgram (for adding subsriptions or other Elmish extensions)
  • Two that take writableModel, update and an optional mapProgram (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!