Send events from JS to a LiveView component

This post was updated 01 May - 2020

Let say you app uses a javascript library that needs to interact with your app. For example a LiveView component. That is possible with the built in Phoenix Channels and Phoenix PubSub. 

### STEP 1 - Generate a channel

Generata a new channel called `App`

mix phx.gen.channel App

In `user_socket.ex` add:

channel "app:*", TutorialWeb.AppChannel

Update the `AppChannel` component so it look like:

defmodule TutorialWeb.AppChannel do
  use TutorialWeb, :channel

  def join("app:" <> token, _payload, socket) do
    {:ok, assign(socket, :channel, "app:#{token}")}

  # can be triggered by from the frontend js by:
  # channel.push('paginate', message)
  def handle_in("paginate", payload, socket) do
    Phoenix.PubSub.broadcast(Tutorial.PubSub, socket.assigns.channel, {"paginate", payload})
    {:noreply, socket}

  def handle_info(_, socket), do: {:noreply, socket}

NOTE. I want every page request to have a unique channel name. And that channel name needs to be exposed to any LiveView component. So fot this purpose, I will use the `csrf-token` than is generated in the page header on every request.

In javascript, I can to this with 

import {Socket} from "phoenix"

let socket = new Socket("/socket", {params: {token: window.userToken}})

let channel = socket.channel(`app:${document.querySelector("meta[name='csrf-token']").getAttribute("content")}`, {})
  .receive("ok", resp => { console.log("Joined successfully", resp) })
  .receive("error", resp => { console.log("Unable to join", resp) })

Now, I have a channel name that looks something like:


So, If I send a channel event in JS to the channel, I have a handle_in-function that handles the event

  def handle_in("paginate", payload, socket) do
    Phoenix.PubSub.broadcast(Tutorial.PubSub, socket.assigns.channel, {"paginate", payload})
    {:noreply, socket}

And in this function, I use Phoenix PubSub to broadcast the event to any subscribers.

### STEP 2 - Subscribe to javascript events

Its easy to set up the a LiveView component to subscribe and handle events from Phoenix PubSub. In the [last tutorial](), I set up a LiveView component for handling pagination. I want to revisit that component and set up a subscription. Open `lib/tutorial_web/live/product_list_live.ex` and in the `mount`-function add:

if connected?(socket), do: Phoenix.PubSub.subscribe(Tutorial.PubSub, "app:#{csrf_token}")
So it looks like: 

  def mount(%{"csrf_token" => csrf_token} = _session, socket) do
    if connected?(socket), do: Phoenix.PubSub.subscribe(Tutorial.PubSub, "app:#{csrf_token}")

    assigns = [
      conn: socket,
      csrf_token: csrf_token

    {:ok, assign(socket, assigns)}

This means, when the component is connected, subscribe to anything that happens on the channel that corresponds to the curren `csrf_token`. This means, that if the channel will be unique per browser request.

The last piece here is to actually handle the messages we subscribe to. 

Add in the same file:

  def handle_info({"paginate", %{"page" => page}}, socket) do
    {:noreply, live_redirect(socket, to: Routes.live_path(socket, ProductListLive, page: page))}

  def handle_info(_, socket), do: {:noreply, socket}

### STEP 3 - Send the message from JS

Now, that the backend code is wired up, I can add the final piece of the javascript. I will visit the page: `http://localhost:4000/products` and expect that I will send a pagination event after 5 seconds and navigate to page 3 `http://localhost:4000/products?page=3`

setTimeout(() => {
  channel.push('paginate', {page: '3'})
}, 3000)

### Final Result

After a few moments the time out in `setTimeout` will kick in and the page will live_redirect to page 3


Related Tutorials

Published 03 Mar - 2020
Updated 01 May - 2020

Share LiveView state between tabs

Each LiveView on each tab spawns a separate state. That might or might not be the desired behaviour. In this tutorial, I am going to share state bet..

Published 28 Jan - 2020
Updated 01 May - 2020

Phoenix LiveView and Invalid CSRF token

One issue that is common to run into is a CSRF error when posting some sort of form rendered with LiveView. The issue is that a LiveView component i..