Skip to content

Example in Browser.Events.onClick doc does not work (click event evaluated twice) #62

@ymtszw

Description

@ymtszw

The example is as follows:

Subscribe to mouse clicks anywhere on screen. Maybe you need to create a custom drop down. You could listen for clicks when it is open, letting you know if someone clicked out of it:

import Browser.Events as Events
import Json.Decode as D

type Msg = ClickOut

subscriptions : Model -> Sub Msg
subscriptions model =
  case model.dropDown of
    Closed _ ->
      Sub.none

    Open _ ->
      Events.onClick (D.succeed ClickOut)

The stated use case is exactly what I wanted to achieve. So I tried, but it seems not working.
https://ellie-app.com/4r8RQNZX4P4a1

module Main exposing (main)

import Browser
import Browser.Events
import Html exposing (..)
import Html.Attributes exposing (style)
import Html.Events exposing (onClick)
import Json.Decode exposing (succeed)


type alias Model =
    { dropDown : Bool }


initialModel : () -> ( Model, Cmd Msg )
initialModel _ =
    ( { dropDown = False }, Cmd.none )


type Msg
    = DropDown Bool


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        DropDown isOpen ->
            ( { model | dropDown = isOpen }, Cmd.none )


view : Model -> Html Msg
view model =
    div []
        [ button [ onClick (DropDown (not model.dropDown)) ] [ text "Drop Down" ]
        , ul
            [ style "border" "solid black 1px"
            , style "display" <|
                if model.dropDown then
                    "block"

                else
                    "none"
            ]
            [ li [] [ text "Item 1" ]
            , li [] [ text "Item 2" ]
            ]
        ]


subscriptions : Model -> Sub Msg
subscriptions model =
    let
        _ =
            Debug.log "subCalled" model
    in
    if model.dropDown then
        -- Browser.Events.onClick (succeed (DropDown False)) -- Toggle this line to see the bug
        Sub.none

    else
        Sub.none


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

When you uncomment the commented line above to provide "click out", the dropdown button itself stops working.
Dropdown part is not shown at all even if you click the button.

Apparently, a click event is evaluated twice, within a single frame, before AND after model update?
To prove that, I introduced "step" state before activating "click out" subscription:
https://ellie-app.com/4r9djjXTZ4Fa1

module Main exposing (main)

import Browser
import Browser.Events
import Html exposing (..)
import Html.Attributes exposing (style)
import Html.Events exposing (onClick)
import Json.Decode exposing (succeed)


type alias Model =
    { dropDown : DD }


type DD
    = JustOpened
    | ReadyToClose
    | Closed


initialModel : () -> ( Model, Cmd Msg )
initialModel _ =
    ( { dropDown = Closed }, Cmd.none )


type Msg
    = Open
    | GetReady
    | Close


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Open ->
            ( { model | dropDown = JustOpened }, Cmd.none )

        GetReady ->
            ( { model | dropDown = ReadyToClose }, Cmd.none )

        Close ->
            ( { model | dropDown = Closed }, Cmd.none )


view : Model -> Html Msg
view model =
    div []
        [ button
            [ onClick <|
                case model.dropDown of
                    Closed ->
                        Open

                    _ ->
                        Close
            ]
            [ text "Drop Down" ]
        , ul
            [ style "border" "solid black 1px"
            , style "display" <|
                case model.dropDown of
                    Closed ->
                        "none"

                    _ ->
                        "block"
            ]
            [ li [] [ text "Item 1" ]
            , li [] [ text "Item 2" ]
            ]
        ]


subscriptions : Model -> Sub Msg
subscriptions model =
    case model.dropDown of
        JustOpened ->
            Browser.Events.onAnimationFrame (\_ -> GetReady)

        ReadyToClose ->
            Browser.Events.onClick (succeed Close)

        Closed ->
            Sub.none


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

This works, since it avoids suspected "double evaluation" by delaying state transition with onAnimationFrame.

Actually, this problem had alreaday reported when it was Mouse.clicks in Elm 0.18.
https://discourse.elm-lang.org/t/mouse-clicks-subscription-created-and-executed-following-click-event/1067
The problem continued in Browser.Events.onClick but I do not see the issue on the repository, so let me file it here.

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