fabulous-dev / Fabulous

Declarative UI framework for cross-platform mobile & desktop apps, using MVU and F# functional programming

Home Page:https://fabulous.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Bug] UI doesn't reflect Model

babusri opened this issue · comments

Code snippet

let returnZeroForEmpty num = if (num.Equals("")) then "0" else num
let roundit txt = txt |> returnZeroForEmpty |> float |> System.Math.Round
let rtm txt = (roundit txt) * 1.0<lb>

let view (model: Model) dispatch =
                             yield View.Entry(placeholder="Mass in pounds", text= model.m",
                                         keyboard=Keyboard.Numeric,
                                         textChanged=(fun e -> e.NewTextValue |> Update |> dispatch),
                                         completed=(fun text -> text |> Update |> dispatch))

update msg model

   | Update txt -> {model with m = (txt |> rtm)}, Cmd.ofMsg AnotherMsg

Steps to reproduce

In the Entry UI, enter text 100.7
The moment you enter 7, the UI shows 101 which is correct. Both model.m and UI have value 101.

Now Enter text 100.3
When you enter 3, the UI shows 100.3 (wrong) and the model.m has value 100 (correct).
I checked in Visual Studio debugger.


Is this a bug in the code that compares 2 models?
The previous model had a value of 100 for m.
When I entered 3, rtm method will round 100.3 and return 100.0.
The new model has a value 100.0 which is same as the old value.
As the old and new value of model.m is 100, the UI is not updated. It still shows 100.3
The UI should show 100 and not 100.3

Is my analysis correct?

For now, as a workaround, I have replaced System.Math.Round with System.Math.Ceiling.

@babusri
your analysis is correct. The old and new value is the same and that's why the ui is not updated.
This should be a correct behavior of the comparison of the two models.

But maybe @TimLariviere can give his opinion on this.

@babusri Ah looks like one of those few gotchas when dealing with validation in Fabulous.

Fabulous will only compare the models to detect changes.
If the model does not change, then Fabulous will consider that nothing changed.

In your case, you're directly storing the validated input in your model instead of what actually on the UI.
So this happens:

Input "100." -> Validated to "100" (stored in model)
Input "100.7" -> Validated to "101" (also stored in model) which is different than the previous 100 -> Fabulous updates UI

Input "100." -> 100 stored in model
Input "100.3" -> Validated to "100" -> same value than previous model -> Fabulous does not update anything.  -> UI stays with 100.3

To avoid that, you'll need to store the "dirty" value directly in your model and only validate it later on, so that Fabulous can see what you really want.

type Model =
    { Value: string }

type Msg =
    | ValueChanged of string

let validateValue value =
    let validatedValue = value |> rtm
    if value <> validatedValue then
        Some (ValueChanged validatedValue)
    else
        None

let update msg model =
    match msg with
    | ValueChanged newValue -> { model with Value = newValue }, Cmd.ofMsgOption (validateValue newValue)

let view model dispatch =
    View.Entry(
        text = model.Value,
        textChanged = fun e -> dispatch (ValueChanged e.NewTextValue)
    )

This will let Fabulous know about what your user wrote, and what you really want your value to be.
It will happen instantly, so your user won't see the difference.

@TimLariviere Thanks for the detailed explanation. Sorry for the late reply. I checked this page only today.