Skip to content

Lazy is too lazy for inputs #189

@lydell

Description

@lydell

SSCCE

https://ellie-app.com/vGfNrrxcb5ba1

module Main exposing (main)

import Browser
import Html exposing (Html)
import Html.Attributes
import Html.Events
import Html.Lazy


type alias Model =
    { text : String
    , lazyText : String
    }


initialModel : Model
initialModel =
    { text = ""
    , lazyText = ""
    }


type Msg
    = GotText String
    | GotLazyText String


update : Msg -> Model -> Model
update msg model =
    case msg of
        GotText text ->
            { model | text = String.filter Char.isDigit text }

        GotLazyText text ->
            { model | lazyText = String.filter Char.isDigit text }


view : Model -> Html Msg
view model =
    Html.div []
        [ Html.p [] [ Html.text "These inputs only allow you to type digits. However, it doesn’t really work for the lazy one." ]
        , Html.label []
            [ Html.text "Regular: "
            , viewText model.text |> Html.map GotText
            ]
        , Html.hr [] []
        , Html.label []
            [ Html.text "Lazy: "
            , Html.Lazy.lazy viewText model.lazyText |> Html.map GotLazyText
            ]
        ]


viewText : String -> Html String
viewText text =
    Html.input
        [ Html.Events.onInput identity
        , Html.Attributes.value text
        ]
        []


main : Program () Model Msg
main =
    Browser.sandbox
        { init = initialModel
        , view = view
        , update = update
        }

The above code renders two inputs that are identical, except that one of them uses Html.Lazy.lazy. As you type in them, non-digits are filtered away.

Html.Lazy.lazy is not supposed to change behavior (only performance), but in this case it unfortunately does: Non-digits are not filterned away (until you type an actual digit).

Here’s what happens:

  1. The user types “a”.
  2. The browser updates .value of the input to "a".
  3. The browser fires the input event.
  4. The Elm update code filters "a" to "".
  5. view is called.

For the regular input:

  1. No diff for value is detected compared to the previous virtual DOM (it is still the empty string). However, value is special cased and is updated anyway. https://github.com/elm/virtual-dom/blob/1.0.3/src/Elm/Kernel/VirtualDom.js#L511 + https://github.com/elm/virtual-dom/blob/1.0.3/src/Elm/Kernel/VirtualDom.js#L927
  2. The result is that the user never sees “a” in the input field.

For the lazy input:

  1. Since the argument to lazy is unchanged (still the empty string), diffing is skipped.
  2. The result is that the “a” that the browser put into the input field is left behind (even though the model says it should be the empty string).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions