LiveView + Channels

Hi all,

I have an application that relies on Phoenix Channels via PhoenixJS and a bunch of React/JS, I’m trying to port it to LiveView hoping to reduce the amount of JS code.

However, I’m getting an error when naively trying to join a channel from the LiveView, specifically a MatchError when trying to call assign on the socket. Presumably because the LiveView socket is somehow not compatible with the Channels socket.

I googled a bit and found https://elixirschool.com/blog/live-view-with-presence/, on its “Extending the LiveView Socket” section it extends the LiveView socket but that does not seem to work either.

Any ideas as to how to best tackle this?

Thanks in advance!

Well you need to provide some minimal code to show what you are trying to achieve, but I guess you may be failing to check that the socket is connected.

This is how I subscribe to a channel from a live view:

def mount(_params, _session, socket) do
  case connected?(socket) do
    true ->
      Phoenix.PubSub.subscribe(Tasks.PubSub, topic)
    
    false ->
      # Only subscribes when Live View is connected via socket
      Logger.info("socket is not connected.")
  end

  {:ok, socket}
end

Hope it helps you to go in the right direction.

To expand a little on this, because it helped me a lot when this part clicked – mount is called twice, in two different Elixir processes. Once when the static HTML page is rendered, and then again when LiveView sets up a persistent process talking to the client via WebSocket.

The first mount is not connected? but the second one is.

1 Like

The first time mount is called is when Phoenix sends back the static html requested by the Browser, that contains all the Phoenix LiveView JS goodness.

Second time mount is called is after the html being rendered by the browser on the initial page load, and this is when the Phoenix LiveView JS makes a call to the server to establish the socket connection in order to mount the LiveView.

Hope that I was clear enough, but if you watch this free course by @mikeclark then you will have a pretty solid understanding about the life cycle of Live View.

Thank you both for your replies. I also had to deal with the double mount but that’s all working fine, I think mine is a conceptual problem, probably from misunderstanding Channels and PubSub.

I have a fair bit of code in my Channels module that was the main point of interaction with the system, something like:

defmodule AppWeb.RaceChannel do
  use AppWeb, :channel
  alias App.Races

  def join("races:" <> race_id, _params, socket) do
    with {:ok, _} <- Races.get_race(race_id) do
      {:ok, assign(socket, :race_id, race_id)}
    else
      :error -> {:error, :no_such_race}
    end
  end
end

Along with some handle_in functions for more interaction which I think is quite standard. I have a fair bit of logic in this module.

Now that I have a LiveView I thought it should serve as the “client” for this Channel, sending the same join and event messages the JS client used to send. I now feel that is incorrect and the Channels module is meant to be used from the JS client only and providing a specific socket.

Instead I think the LiveView should be in charge of communicating with the frontend via it’s own socket, which makes total sense and I should move the channel logic elsewhere, but where?

I feel like it should be a specific module relying on PubSub “manually” as described in the PubSub docs. Would that be the way to go?

Thanks again for your help :slight_smile:

So I tried the approach in https://elixirschool.com/blog/live-view-with-channels/ adapting the code from the LiveView.Socket and changing to the new Phoenix APIs as required as the article is a bit old by now, but I hit a snag with Presence which seems to require a channel socket to work? Still, it does feel a bit odd to me to copy/paste the Phoenix.LiveView.Socket code and extend it.

Anyway, feels like I’m not going to be able to use LiveView to completely replace the JS code at this point in time. :cry: