With release of hooks we got an ability to push events from client to server via pushEvent(event, payload)
. But is there a way to do the opposite - push an event from a LiveView
module on server to a hook on client?
An ideal implementation looks like this in my mind. The server part:
defmodule MyAppWeb.SomeLive do
# ...
def handle_event("whatever", _params, socket) do
# Here I do something with the socket to push an event
message = "custom-message"
payload = %{foo: "bar"}
push_custom_message(socket, message, payload)
# And return socket as is, without assigning any changes
{:noreply, socket}
end
end
The client part:
// ...
export const liveSocket = new LiveSocket("/live", Socket, {
hooks: {
SomeHook: {
// And here I receive all custom messages sent from server via `push_custom_message/3`
messageReceived(message, payload) {
console.log(message); // "custom-message"
console.log(payload); // {foo: "bar"}
}
},
}
})
// ...
The point is that some parts of UI are tightly controlled by JS, and there is no way I can render them on the server, and the most convenient way for me is to trigger an event from the client, receive it on the server and sent back some data through WebSocket.
As an example, imagine an infinite scrolling widget fully controlled by JS, with phx-update=ignore
, and when I click the “Load More…” button a “load_more” event is sent to LiveView
module on the server, which then queries a DB for more data, and sends it back through WebSocket, where it’s received by the hook and rendered in the widget (please note, that this is just an example, the simplest I managed to invent to demonstrate the problem, so please don’t try to solve “how to build an infinite scrolling in LiveView”, that’s not the point).
At the same time I’m pretty happy with all other UI parts to be controlled by LiveVIew, so I don’t want to go all-in with a full SPA. I also don’t want to establish a separate API endpoint (or a socket connection) for this specific widget - because why should I, if there is already a socket established by LiveView? If I was able to send messages through it - it would fit just fine. But I haven’t found a way to.
Also, I’m not experienced neither in Elixir, nor Phoenix, so I might be wrong, but as far as I understood from the code, if I used a regular Phoenix.Channel
, I’d be able to do this - to send messages from server to the client. But it seems that in Phoenix.LiveView.Channel
the “real” Phoenix.Socket
is decorated with Phoenix.LiveView.Socket
, and there is no way I can access the former. I also can’t find any suitable functions available externally in Phoenix.LiveView.Channel
.
There is also an obvious workaround - I can just render the data I want into the DOM node controlled by the hook, and pick it up on updated
, but I believe the socket way would be more correct.