Send events from JS to a LiveView component

This post was updated 01 May - 2020

csrf liveview phoenix javascript

Let's say your 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

Generate 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 looks 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 for this purpose, I will use the csrf-token that is generated in the page header on every request.

In JavaScript, I can do 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

It's easy to set up 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/tutorialweb/live/productlist_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 current csrf_token. This means that 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 timeout 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..