Hook for preventing LivePatch not working

I have a clickable table row with a button inside. Desired behavior would be:

  • When clicked on row, opens sidepane
  • When clicked on button inside row, does button event

What happens is, since button is inside the row, both things happen, i.e. button does his thing and sidepane opens. Which is a bug, I don’t want sidepane to open when button is pressed.

What I was advised to do was adding a hook, which I did.
In app.js I have:

let Hooks = {}

Hooks.AllowButtonDefaultAndPreventLinkDefault = {
  mounted() {
    this.el.addEventListener("click", (e) => {
      // `closest` in case we click an element inside a button, e.g. an icon.
      if (e.target.closest("div > a")) {
        e.preventDefault()
      } else {
        location.href = this.el.href
      }
    })
  }
}

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {hooks: Hooks, params: {_csrf_token: csrfToken}})

Code:

@impl Phoenix.LiveComponent
  def render(assigns) do
    ~H"""
    <LivePatch
      to={{ @path }}
      class={{ "table-row #{row_classes(@is_selected)} device-row" }}
      opts={{ [id: @device.id] }}
    >
      <div class="table-cell p-5">{{ @device.hardware_id }}</div>

      <div class="table-cell p-5">
        <DevicePresenceStatus status={{ presence_status(@device, @devices_presence) }} />
      </div>

      <div class="room-name table-cell p-5">{{ room_name(@device.room) }}</div>
      <div class="department-name table-cell p-5">{{ department_name(@device.room) }}</div>
      <div class="table-cell p-5">{{ @device.signaling_channel_arn }}</div>

      <div class="table-cell p-5">
        <button
          :on-click={{ @reboot_device }}
          :phx-hook="AllowButtonDefaultAndPreventLinkDefault"
          phx-value-device_id={{ @device.id }}
          class="hover:bg-blue-dark cursor-pointer flex rounded p-3 bg-blue text-xs justify-center font-medium text-white uppercase"
        >
          Reboot Device
        </button>
      </div>
      <div class="table-cell" />
    </LivePatch>
    """
  end

Web tools:

The hook does load but it does nothing. I tried adding phx-ignore as said in this post but it did nothing.

Hope somebody can hook me up with an answer :slight_smile:

Hi @ken-kost!

I believe you just have replace :phx-hook with phx-hook. As a general rule, Surface doesn’t have any directive starting with phx-. There is, however, a :hook directive that you can use if you want to have the hook code in a colocated JS file along with you component’s file. For more info, see Surface’s JS Interoperability page.

Thanks for the answer, it was helpful but not enough to fix the issue. Luckily a college figured the other parts.

In case somebody will stumble upon this:

We also needed to add
e.stopImmediatePropagation() under e.preventDefault()

Button now uses :on-capture-click instead of :on-click, has an id and uses phx-hook instead of :phx-hook as you mentioned :tada:

And another note, in order for the test to pass, using Phoenix.LiveViewTest module, instead of using render_click we used render_hook of course :slight_smile: