Skip to content

Input is losing focus on re-draw when using Bind.el #92

@MangelMaxime

Description

@MangelMaxime

The introduction is a bit long, but at the end there is a small reproduction code. I felt like explaining by context could help to understand the why

The way Fable.Form works is, by composing onChange functions it is able to handle any number of inputs via a single onChang function.

In a pure React application, the code looks like that:

[<ReactComponent>]
let View () =
    let formState, setFormState =
        {
            Email = ""
            Password = ""
            RememberMe = false
        }
        |> Form.View.idle
        |> React.useState

    let onSubmit =
        fun (_formResult: FormResult) ->
            { formState with
                State = Form.View.Success "You have been logged in successfully"
            }
            |> setFormState

    Form.View.asHtml
        {
            OnChange = setFormState
            OnSubmit = onSubmit
            Action = Form.View.Action.SubmitOnly "Sign in"
            Validation = Form.View.ValidateOnSubmit
        }
        form
        formState

With Elmish:

let view (model: Model) (dispatch: Dispatch<Msg>) =
    Form.View.asHtml
        {
            OnChange = FormChanged >> dispatch
            OnSubmit = dispatch
            Action = Form.View.Action.SubmitOnly "Sign in"
            Validation = Form.View.ValidateOnSubmit
        }
        form
        model

So I tried to do something similar with Sutil, and naively I used Bind.el which seems to allows to depends on a store (to me it looked similar to React hooks):

let app () =
    let stateStore =
        {
            Email = ""
            Password = ""
            RememberMe = false
        }
        |> Form.View.idle
        |> State.Filling
        |> Store.make

    bulma.section [

        Bind.el(stateStore, fun state ->
            match state with
            | State.Filled (email, password, rememberMe) ->
                Html.div [
                    Html.h1 [
                        prop.text "Form filled"
                    ]
                    Html.p [
                        prop.text $"Email: %s{email}"
                    ]
                ]

            | State.Filling formValues ->
                Form.View.asHtml
                    {
                        OnChange = fun values ->
                            printfn "Form values changed: %A" values
                            State.Filling values |> Store.set stateStore
                        OnSubmit = fun result -> State.Filled result |> Store.set stateStore
                        Action = Form.View.Action.SubmitOnly "Sign in"
                        Validation = Form.View.ValidateOnSubmit
                    }
                    form
                    formValues
        )
    ]

The problem is that when the stateStore is updated, Sutil re-draw the DOM and the input lose focus.

CleanShot.2024-09-20.at.22.17.16.mp4

Looking at the documentation, I found that Bind.attr ("value", ...) should be probably be used. I could perhaps, use Store.map in order to mimic how Fable.Form compose function internally but it would require quite a lot of work and means that I can't share as much logic between the different renderer as I would like.

So I am asking if it is possible to keep the focus, without using Bind.attr or is it the only way and I should look into trying to compose/map the store internally directly?


Below is a simplify code to reproduce the situation:

let inputStore = Store.make ""

Bind.el (inputStore, fun input ->
    Html.div [
        Html.div [
            prop.text $"Input value: %s{input}"
        ]

        Html.input [
            Ev.onInput (fun ev ->
                let value: string = ev.target?value
                Store.set inputStore value
            )
            prop.value input
        ]
    ]
)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions