Elm Virtual Dom bug

Elm’s vdom diffing considers a node created via lazy different than a node created without lazy, even if the results are equivalent. In compiled Elm output, in the vdom diffing code, are the comments:

// Bail if you run into different types of nodes. Implies that the
// structure has changed significantly and it's not worth a diff.

“not worth a diff” means nodes will be deleted and recreated (even if they would be equivalent). This can cause surprising performance and UX problems if one uses lazy conditionally.

Here’s an example of an input that loses focus when it switches between lazy/non-lazy rendering.

https://ellie-app.com/jZhzsYf97Cga1

1 Like

Html.Lazy.lazy and Html.map create virtual DOM nodes (yes, they do create nodes, not just modify existing ones!), and the vdom library diffs trees of virtual DOM nodes. But as a user of the package, I tend to think about the real DOM tree: I think there’s a 1:1 mapping between virtual DOM nodes and real DOM nodes, but as you’ve discovered the virtual DOM tree can contain more nodes, which is surprising.

Glad to hear you managed to figure it out. It certainly is surprising that lazy would cause nodes to be recreated in a situation where not using lazy would not!

For your particular application, can you just leave the lazy out? Or move down into to other parts of the view code where it will not upset your inputs?

I can just make calls to lazy even in the cases I know it will “fail” to be lazy.

Btw, the generalization of this is: Elm’s vdom diffing considers a nodes of different types not to be equal, without doing any extra smart things to see if the results would be equivalent.

Node types are (virtual-dom/src/Elm/Kernel/VirtualDom.js at 44cbe2bf3d598cab569045cefcc10de31907598d · elm/virtual-dom · GitHub):

  • TEXT (Html.text)
  • NODE (Html.div, Html.a etc)
  • KEYED_NODE (Html.Keyed.node etc)
  • CUSTOM (markdown and webgl I think)
  • TAGGER (Html.map)
  • THUNK (Html.Lazy.lazy etc)

So Html.div [] [ Html.a [] [ Html.text "" ] ] results in:

NODE
  NODE
    TEXT

While wrapping that in lazy results in:

THUNK
  NODE
    NODE
      TEXT

The vdom compares the parent nodes and sees that NODE !== THUNK and bails. I don’t think that’s a bug – it sounds like maybe you are requesting smarter diffing.

1 Like

Have you done anything with this in elm-safe-virtual-dom? It strikes me that there are definitely some improvements could be made for both lazy and keyed.

I haven’t tested yet, but I suspect that elm-safe-virtual-dom does not have this problem since I compare with the DOM, not two vdom trees.

1 Like

lazy is documented as an optimization. It is implied that we’ll always get the same result, but perhaps faster. Yet, here is a case where using lazy breaks the end user’s experience.

If this was clearly documented, we could call it a caveat. In this case, I’d say bug, insofar as it is unexpected and breaks things for the end user. It took me a few days to track down this undesired behaviour and I had to post-process my compiled elm to log diffing/patching to figure out what was happening. Whether or not to document this, “fix” it, or sweep it under the rug is another issue (not mine). Or perhaps the compiler could warn if an expression returns lazy and non-lazy nodes?

If you have the time, please create a GitHub Issue on the elm/virtual-dom package, with your code from the Ellie. I know it seems kind of pointless but a future me that finds time to continue with elm-janitor would definitely find it helpful to have this documented there.

2 Likes

@rupert done.

1 Like

The way I see it, the only promise lazy makes is that it sometimes won’t call your Elm function. It says nothing about the DOM.

Note: I’m not trying to argue with you. It is unexpected behavior you are demonstrating.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.