fsprojects / Avalonia.FuncUI

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

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

Open MaxWilson opened 6 months ago

MaxWilson commented 6 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 6 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 6 months ago

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

JaggerJo commented 6 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 6 months ago

@MaxWilson we could make the default configurable.