Sending events from JS to LiveView component

I know I can have heex attributes like phx-click="eventid" in the markup and then

def handle_event("eventid", _, socket), do: [...]

But how does one send that event without actually interacting? There are some examples in the guides like:
https://hexdocs.pm/phoenix_live_view/js-interop.html
yet they seem to cover different cases:

  • handle custom client-side JavaScript when an element is added, updated, or removed by the server
  • Triggering phx- form events with JavaScript

but what I need is best described as “triggering arbitrary event that can be handled with handle_event/3 with JavaScript”. I found something similar here:

yet it’s been some time and many Phoenix changes since that fine piece was written.

How would you approach this task today?

1 Like

You can send arbitrary events to your component from JS by using pushEvent : JavaScript interoperability — Phoenix LiveView v0.17.11

You just need to attach a Hook to your component and call pushEvent from there.

The hooks part of the guide says: “To handle custom client-side JavaScript when an element is added, updated, or removed by the server […]”. In my case there is no user interaction, no server “interaction”, nothing is mounted, added/removed nor updated. It’s just a JS callback function, which gets called by another piece of JS code. I’d like that JS callback to notify backend about the fact that it was called.

You need a hook attached to the live_component that will receive the events. This hook will be initialized on mount.

defmodule MyLiveComponent do
  def render(assigns) do
    ~H"""
    <div phx-hook="MyLiveComponentHook">
       ...
    </div>
    """
  end

  def handle_event("custom-event", _params, socket) do
    {:noreply, socket}
  end
end

Then in your hook, listen for custom JS events and propagate them to your LiveComponent.

export const MyLiveComponentHook = {
  mounted() {
    window.addEventListener("my-app:custom-event", => {
      this.pushEvent("custom-event", {})
    });
  }
}

You can now trigger JS events from wherever you want, they will be handled by the hook and propagated to your LiveComponent

const event = new Event('my-app:custom-event');
window.dispatchEvent(event);

I just wrote this code directly on the forum, I hope this is kind of working :man_shrugging:

1 Like

One can also look at @cblavier’s answer from the bottom up.

You have something happening somewhere in your JS (your callback) and want it to notify a LV or LiveComponent. How would you tell whatever API you call where things should be routed to? There’s no module names or fixed IDs you could hardcode there. Your LV or component might even be present multiple times on the current page. So your best best is some form of PubSub, where producers of data don’t need to know consumers upfront. That’s the dispatchEvent part.

Now how do you consume those dispatched events in a way that the can be forwarded to your LV pieces/server. That’s where hooks come in. They connect some JS with a section of markup, which LV knows how to connect to things on the server. Hooks do receive LV lifecycle events that’s true, but that doesn’t mean all they do needs to be based on lifecycle events. You can also listen for any other things happening and react to those as well. That’s the addEventListener part.

So now the hook receives events. Then you can use pushEvent to push data forward to the server to handle in an handle_event callback.

2 Likes

Other than two small typos (missing set of empty parens and “addEventListeRner”) it does :smiley: thank you!

Yes, that’s a very good explanation, thank you!

What I think I was hoping for was something like for example phx-js="eventid" (form/syntax aside) and the rest would be already set, similar to how e. g. phx-click= and Co. is working but the way you explained it is fine too. I should probably push a PR updating the docs so that it is clear that not only “form events” and “lifecycle events” are effortlessly handled this way.

1 Like

Sorry to necropost but I’m stuck w this issue. Both of these issues occur for me. I can receive from the server (on the client), but cannot push to the server.

  1. unknown hook found for..confHook
  2. LiveSocket.pushEvent is not a function
#server
 <div id="some-id" phx-hook="confHook" phx-click="select_user" phx-target={@myself}>HERE</div>

#client
let Hooks = {}
  Hooks.confHook = {
    mounted(){
      window.addEventListener("phx:show_confirm", e => {
        if (confirm("Are you sure you want to perform this action?")) {
            this.pushEvent("load-more", {})
              # User confirmed, perform the action
            } else {
              # User canceled
            }
      })
    },
  }

What does your app.js look like? Having only those errors as clues, they lead me to believe that you haven’t passed your hooks into your liveSocket constructor.

let liveSocket = new LiveSocket("/live", Socket, {
  params: {
    _csrf_token: csrfToken
  },
  hooks: Hooks # <-----------
 })

This isn’t done by default by mix phx.new, you have to do it manually.

If you’ve done this already then sorry!

Well I’ve deleted and discarded the non-working code now. But yeah, I was passing in the hooks like that.