Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate event handlers being added to elements with ids during LiveView update. #1171

Closed
cthulahoops opened this issue Mar 11, 2021 · 5 comments · Fixed by #1175
Closed

Comments

@cthulahoops
Copy link
Contributor

I've been running into a weird bug with event handlers being added more than once to elements when using AlpineJS with LiveView.

This is a fairly minimal demonstration:

<script>
function add_message(dispatch) {
  event.preventDefault()
  let message = document.getElementById("message").value;
  dispatch('add_message', {message: message});
}
</script>

<ul id="to-input-div" x-data="{}" phx-hook="PushEvent" phx-push-event="add_message">
<%= for message <- @messages do %>
  <li><%= message %></li>
<% end %>
  <input id="message" x-on:keydown.enter="add_message($dispatch)">
  <button x-on:click="add_message($dispatch)">Add (no id)</button>
  <button id="unreferenced" x-on:click="add_message($dispatch)">Add (with id)</button>
</ul>

When you enter a message and hit enter it gets added once, and a second copy of the event handler is added. The second message is added twice, the third four times, the fourth eight.... Clicking the button with an id does the same thing, but clicking the button without id only adds it once. (Checking registered event handlers confirms that an event handler has been added for every message added to the list.)

This is the phx-hook:

Hooks.PushEvent = {
  mounted() {
    const eventName = this.el.attributes['phx-push-event'].value;
    this.el.addEventListener(eventName, (event) => { this.pushEvent(eventName, event.detail) })
  }
}

And I have the callback calling Alpine.clone from onBeforeElUpdated.

(Running alpinejs 2.8.1 and phoenix_live_view 0.15.4).

@cthulahoops
Copy link
Contributor Author

The difference in behaviour occurs because morphdom uses the node's id to identify nodes that should be moved rather than deleted and reinserted. I think the bug occurs because element moves aren't distinguished from element creations.

With this in mind, I can write an version that triggers the issue without liveview:

<div id="to-input-div" x-data="{'x': 0}">
  <span x-text="x">0</span>
  <button id="a" x-on:click="x += 1; $el.appendChild(document.getElementById('a'))">A</button>
  <button id="b" x-on:click="x += 1; $el.appendChild(document.getElementById('b'))">B</button>
  <button id="c" x-on:click="x += 1; $el.appendChild(document.getElementById('c'))">C</button>
</div>

I expect that clicking a button will increment the counter and move that button to the end. When the buttons get moved duplicate event handlers get attached and the counter starts to go up in steps of more than one.

@lawik
Copy link

lawik commented Mar 22, 2021

I think I'm seeing something very similar. Going to see if the PR solves my issue when it goes through.

@chriischambers
Copy link

Hi guys. So this issue still exists when your event is attached to a node within the node you are moving. I have updated the example to demonstrate this :

<div id="to-input-div" x-data="{'x': 0}">
    <span x-text="x">0</span>
    <div id="a"><button x-on:click="x += 1; $el.appendChild(document.getElementById('a'))">A</button></div>
    <div id="b"><button x-on:click="x += 1; $el.appendChild(document.getElementById('b'))">B</button></div>
    <div id="c"><button x-on:click="x += 1; $el.appendChild(document.getElementById('c'))">C</button></div>
</div>

@HugoDF
Copy link
Contributor

HugoDF commented Apr 19, 2021

Hi guys. So this issue still exists when your event is attached to a node within the node you are moving. I have updated the example to demonstrate this :

<div id="to-input-div" x-data="{'x': 0}">
    <span x-text="x">0</span>
    <div id="a"><button x-on:click="x += 1; $el.appendChild(document.getElementById('a'))">A</button></div>
    <div id="b"><button x-on:click="x += 1; $el.appendChild(document.getElementById('b'))">B</button></div>
    <div id="c"><button x-on:click="x += 1; $el.appendChild(document.getElementById('c'))">C</button></div>
</div>

Are you interested in digging into the issue? Even if you submit a PR that only updates the relevant test to fail, it'll probably make it easier for someone else to solve

Couple of other notes:

@chriischambers
Copy link

chriischambers commented Apr 19, 2021

@HugoDF - I can confirm that the previous fix has not yet been released.

As requested I have opened up a PR with a failing test here - #1265

It would appear that the previous solution tracks in the listenForNewElementsToInitialize method a __x_node_add_count value but this is only being tracked on the parent node. Moving this tracking to the walkAndSkipNestedComponents loop and storing it against the correct node does indeed solve the issue and make my test pass but then this causes several other tests in the suite to fail. At this point I have ran out of time to delve deeper into this.

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants