Search code examples
elm

onClick message causes unwanted refreshing of the page


I'm trying to build a website in Elm and it includes a couple links to change the language:

div [ class "language" ][ a [ class englishClass, onClick (ChangeLanguage English) ] [ text "EN" ]
                        , a [ class germanClass, onClick (ChangeLanguage German) ] [ text "DE" ]
                        , a [ class italianClass, onClick (ChangeLanguage Italian) ] [ text "IT" ]
                        ]

My Update looks like this:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ...  -- Stuff for URLs

        ChangeLanguage language ->
            ( { model | language = language }
            , Cmd.none
            )

type Msg
    = LinkClicked Browser.UrlRequest
    | UrlChanged Url.Url
    | ChangeLanguage Language

This is my model:

type alias Model =
    { key : Nav.Key
    , url : Url.Url
    , language : Language
    }

And this is my init function, which (at least in my mind) defaults to English as a language:

init : flags -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init _ url key = ( Model key url English, Cmd.none )

The problem:

Whenever I click the links that change the language, they do seem to work: I see the language of the paragraphs in the page change, but then several unwanted things happen:

  1. The page refreshes. Since I already saw the language change across the page, it's clear this is not needed so I'd like to avoid it.
  2. As a consequence of 1, the viewport is brought all the way to the top of the page again.
  3. The language changes to the default English again!

How could I avoid 1 (and thus 2) from happening?
And what am I missing to make it so the change is maintained across the site (at least when refreshing the page, since I haven't tried working with sessions or cookies yet)?


Solution

  • I consider this behaviour a bug in Elm, and have filed an issue for it, as have others with associated PRs. But in the year and a half since have received no attention from anyone in a position to actually do something about it, which is unfortunately par for the course with Elm.

    The problem is that Elm "hijacks" onClick on a elements to create navigation events so that anchors can be handled inside Elm. When using Browser.application, clicking an a element will have Elm call onUrlReuqest with a value of Browser.UrlRequest, which will class the URL as either Internal or External and require you to make a decision on what to do with it.

    The problem at the root of this is that omitting href will generate an External UrlRequest with the URL being an empty string. By default, and by the usual handling of External, this will tell the browser to load the URL as usual, thus refreshing the page. But it also suggests a possible workaround is to special-case External "":

    update msg model =
        case msg of
            UrlRequested (Browser.Internal _) ->
                ( model, Cmd.none )
    
            UrlRequested (Browser.External "") ->
                ( model, Cmd.none )
    
            UrlRequested (Browser.External url) ->
                ( model, Browser.Navigation.load url )
    
            UrlChanged _ ->
                ( model, Cmd.none )
    

    Another workaround is to add href="#" to the a elements, which will correctly classify them as Internal