DOM not updated properly when component and parent live view events arrive together on draggable element

I have a button that is draggable inside a live view:

    <button
      id={"#{@id}.drag_handle"}
      phx-hook="algorithm:move_vertex"
      draggable="true"
      data-drag-label={@vertex.label}
      data-drag-vertex={@vertex.id_64}
      class="flex items-center hover:text-gray-500"
    >
      <.icon name="icon-drag" class="w-5 h-5" />
    </button>

Now “sometimes” when I drag and drop the button, it is not updated. Especially data-drag-vertex={@vertex.id_64}.

In the console, with live view debug enabled, I see the diff for the button data attribute. And if I put another HTML element next to the button, that element is always updated.

If I update anything else in the page (not related to the button), I see the diff in the console about that part, but the button will also update correctly to catch up with the previous diff.

I did a test with the following code, putting the button twice. And only the dragged button is not updated. I always have to drag it about 5 times because it trigger the bug.

    <button
      id={"#{@id}.drag_handle"}
      phx-hook="algorithm:move_vertex"
      draggable="true"
      data-drag-label={@vertex.label}
      data-drag-vertex={@vertex.id_64}
    >{@vertex.id_64}
    </button>
    <button
      id={"#{@id}.drag_handle_"}
      phx-hook="algorithm:move_vertex"
      draggable="true"
      data-drag-label={@vertex.label}
      data-drag-vertex={@vertex.id_64}
    >{@vertex.id_64}
    </button>

I use firefox, so maybe it’s a firefox issue.

I realize this is not much to go on, but I was wondering if you had an idea how to debug this or if it was ringing a bell.

UPDATE:

I have noticed something, my hook add a dragend listener with:

(drag_end event doesn’t update any assign in my test)

        this.el.addEventListener("dragend", (evt) => {
            this.pushEvent("drag_end")
        })

and if I change the code to:

        this.el.addEventListener("dragend", (evt) => {
            setTimeout(() => this.pushEvent("drag_end"), 100)
        })

the bug is not triggered. But if I put a low value, like 10 ms in the timeout, the bug appears.

It seems there is some kind of subtle race condition somewhere.

UPDATE 2:

I realized that on drop/dragend it would fire another event that is hover state.

Basically, the two events arrive together, the dragend is at the live view level, but the hover event is at the component level:

[debug] HANDLE EVENT "drag_end" in BookWeb.CMS.IndexLive
  Parameters: %{}
[debug] Replied in 25µs
[debug] HANDLE EVENT "hit_gridpoint" in BookWeb.CMS.IndexLive
  Component: BookWeb.CMS.Algorithm.CanvasComponent
  Parameters: %{"cx" => 1115, "cy" => 524.7999877929688, "x" => 5, "y" => 4}
[debug] Replied in 25µs

If I remove either one, the bug is gone.

UPDATE 3:

With live view profiling enabled I noticed that:

When the bug occurs:

01:27:34.922 morphdom: 5.98ms - timer ended
01:27:34.922 component patch complete: 7.76ms - timer ended 
01:27:34.924 toString diff (update): 0.32ms - timer ended 
01:27:34.928 morphdom: 4ms - timer ended
01:27:34.928 full patch complete: 4.48ms - timer ended 
01:27:34.933 morphdom: 4.68ms - timer ended 
01:27:34.934 morphdom: 0.08ms - timer ended

When it does not:

01:29:30.670 morphdom: 6.66ms - timer ended 
01:29:30.670 component patch complete: 8.42ms - timer ended
01:29:30.676 morphdom: 4.92ms - timer ended
01:29:30.677 morphdom: 0.14ms - timer ended 
01:29:30.684 morphdom: 5.2ms - timer ended 
01:29:30.685 component patch complete: 7.46ms - timer ended 
01:29:30.686 toString diff (update): 0.34ms - timer ended 
01:29:30.691 morphdom: 4.54ms - timer ended 
01:29:30.692 full patch complete: 6.06ms - timer ended

I’m not seeing anything super obvious here, I think we need more details.

Is this drag/drop functionality? Is the element being moved in the DOM? What does the hook do? What do the events do?

Yeah, I realize it’s not much to go on. I will try to make a reproduction.

1 Like

I was able to make a repro!

It is here: GitHub - kuon/bug-phoenix-liveview: Phoenix tailview bug

1 Like

As I was able to make a reproduction, I made an issue:

1 Like

The same assign should not be diverging like that, so there must be a bug somewhere. I don’t see anything suspicious in your hook that would corrupt the state, which was my first thought. I have done drag/drop with LiveComponents and never observed this behavior myself.

Does this still occur if you remove the LiveComponent and render the draggable element directly? That might help narrow it down.

Also, you are pushing an event to the server on every single drag move event, which is quite a few events. It’s a shot in the dark but I wonder if you are triggering a rare race condition or bug somewhere in the LiveView/LiveComponent implementation. Does the bug go away if you debounce the events?

Either way I think this is probably something a maintainer will have to take a look at, so opening an issue is a good idea.

I tested two things:

  • removing the live component (I did add it because I have it in my actual code and I was trying to reproduce)
  • debouncing or not firing dragover at all

None of those remove the bug, but removing dragover does make it less probable, I have to drag the box much more for the bug to actually appears.

1 Like