fsprojects / Avalonia.FuncUI

Develop cross-plattform GUI Applications using F# and Avalonia!
https://funcui.avaloniaui.net/
MIT License
983 stars 74 forks source link

[Elmish] Button.onClick handlers not getting updated by view #379

Open MaxWilson opened 11 months ago

MaxWilson commented 11 months ago

A button, once created, will update its content in response to a new view, but will not update its onClick. This leads to buttons that say they'll do one thing actually doing another.

Repro: 1.) Clone https://github.com/MaxWilson/AvaloniaRepro 2.) Dotnet run 3.) In the app, click on "Add Ryan" and then "Say something Ryan"

Expected: it should say "Ryan: Blahblahblah". Actual: "Bob: Blahblahblah".

type Model = {
    people: string list
    chatLog: string list
    }
type Msg =
    | Speak of person:string * msg: string
    | NewPerson of name:string

let init _ = {
    people = ["Bob"]
    chatLog = []
    }

let update msg model =
    match msg with
    | Speak (person, msg) ->
        { model with chatLog = (sprintf "%s: %s" person msg) :: model.chatLog }
    | NewPerson name ->
        { model with people = name :: model.people }

let view model dispatch =
    StackPanel.create [
        StackPanel.children [
            for person in model.people do
                Button.create [Button.content $"Say something {person}"; Button.onClick (fun _ -> dispatch (Speak (person, "Blahblahblah")))]
            for msg in model.chatLog do
                TextBlock.create [TextBlock.text msg]
            if model.people.Length = 1 then
                Button.create [Button.content "Add Ryan"; Button.onClick (fun _ -> dispatch (NewPerson "Ryan"))]
            if model.people.Length = 2 then
                Button.create [Button.content "Add Sarah"; Button.onClick (fun _ -> dispatch (NewPerson "Sarah"))]
            ]
        ]
MaxWilson commented 11 months ago

Based on https://github.com/fsprojects/Avalonia.FuncUI/issues/369 it looks like the fix is to always specify SubPatchOptions.Always with OnClick.

type Button with
    static member onClick logic =
        Button.onClick(logic, SubPatchOptions.Always)
MaxWilson commented 11 months ago

Is there a reason why Always isn't already the default?

JaggerJo commented 11 months ago

Good question.

In earlier versions of funcUI elmish was the only programming model. With elmish you end up having one gigantic view tree that you need to diff + patch on every update. In this scenario not patching the click property of 100s of buttons on every updates was a valid argument.

Now for the component model.. I'm not so sure.

JaggerJo commented 11 months ago

@MaxWilson we could make the default configurable.