The easiest way to understand the problem, I think, is by starting with the example program in the guide’s section on Browser.application
. In that example, the control flow for clicking on a link looks like this:
<a href="/path"/>
\
\- click on it -->-- Elm runtime >-\
onUrlRequest function
|
v
LinkClicked message
|
v
pushUrl cmd
/-< Elm runtime <-/
onUrlChange function
|
v
UrlChanged message
|
v
update function enacts model update for "/path" URL
There is a double-update in a sense here, because as you can see we have two messages (LinkClicked
and UrlChanged
) flowing through two invocations of our update
function. However, they’re well-ordered and serve two different purposes, pretty clearly explained in the guide.
In 0.18 and earlier, things were not nearly this nice. The documentation for elm-route-url
explains this pretty well, but in short you had a lot of bookkeeping to do on your own. I sank quite a lot of time into trying to make that work before discovering elm-route-url
, and it was a pain.
In the above diagram, it’s important to note that, if you want any data to ride along with the user’s click on that /path
link, you must encode that data in the URL itself. And then you have parse it back out when it comes into (the second invocation of) your update
function.
That parsing work does have to be done if you want to support “deep linking” into your SPA. And the encoding work does also have to be done if you want to generate those deep links. But it feels like a big sacrifice to give up the strongly-typed messages that you see in most Elm architecture examples. :-/
So, given all of that, my app in 0.18 and before works off of div
s that produce messages onClick
. However, doing it that way, as in this modification to the guide’s example code, yields this control flow:
<div onClick="Goto"/>
\
\- click on it -->-- Elm runtime >-\
Goto message
|
v
pushUrl cmd
/----< Elm runtime <----/ |
onUrlChange fn |
| |
v v
UrlChanged message --> update fn enacts model update for Goto message
This is the double update problem: there are two arrows going into the update
function that both want to enact the model update. If you run the code, you’ll see that clicking on the <a/>
s increments the counter by one, but clicking the <div/>
increments it by two. (You can’t just ignore the UrlChanged
message because that’s the only one that happens on back/forward navigation.)
So, if you take the approach of driving your app with div onClick
rather than a href
, then elm-route-url
is still necessary in order to take care of the bookkeeping and prevent the double updates. Unfortunately we’re still working on updating it for 0.19 – help is appreciated!
(This example program just needs some very trivial bookkeeping to make it work, but in general it’s more complex.)
I think it would be a nice addition if pushUrl
took a type ChangeNotification = InvokeUrlChange | SkipUrlChange
argument; that would really alleviate the problem.