Phoenix.js Channel Presence onJoin firing multiple times

I’m trying to use a Phoenix Channel for the signaling part of a WebRTC application, as I thought Presence would be perfect for this.

The thing is: I’m trying to track a user join using Phoenix.js’ Presence object, like this:

  presence.onJoin(async (id, current, newPres) => {
    if (!current) {
      const peerConnection = await createPeerConnection(
      peerMap.set(id, peerConnection);
    } else {

The thing is: every time a user joins, this callback is fired multiple times, even with its own Presence object and the objects of users that were previously in the channel. I don’t know if this is the expected behavior and couldn’t find a reference about that.

My Channel looks like this right now:

defmodule ElmWebRtcWeb.VideoChannel do
  use Phoenix.Channel
  alias ElmWebrtcWeb.Presence

  def join("videoroom:" <> _channel, _message, socket) do
    send(self(), :after_join)
    {:ok, socket}

  def handle_info(:after_join, socket) do
    {:ok, _} =
      Presence.track(socket, socket.assigns.user_id, %{
        online_at: inspect(System.system_time(:millisecond))

    push(socket, "presence_state", Presence.list(socket))
    {:noreply, socket}

and the JS Presence is created like this:

  const channel =`videoroom:${room}`, {});
  const presence = new Presence(channel);

Does anyone have a clue about:

  • Is this the expected behavior of Presence.js?
  • If so, is there any way for me to achieve what I want with it?
  • If not, what could be going wrong?

Thank you very much!


I haven’t been able to use this with const presence = Presence(channel), but this:

  let presences = {};

  const onJoin = async (id: string | undefined) => {
    //onJoin implementation

  const onLeave = async (id: string | undefined) => {
    //onLeave implementation

  channel.on('presence_diff', (diff) => {
    presences = Presence.syncDiff(presences, diff, onJoin, onLeave);

did the job and is working perfectly.

I’m still wondering if the first implementation should work though :thinking:


I found the exact same thing when working with onJoin() - it would fire more than expected.

My workaround has been to use onSync() instead and then calling presence.list(), which is always (in my experience) accurate.

For example:

  presence.onSync(() => {
    const myParticipants = presence.list();
    // …  and so on

Hope that helps!


Chiming in to add (somewhat late, but for anyone else who finds this thread) that presence.onJoin isn’t working as expected (or in my case, at all), with the following code taken directly from the docs:

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


roomChannel ='room:blahblahblah')

roomChannel.join().receive("ok", response => {
// code omitted

let presence = new Presence(channel)

presence.onJoin((id, current, newPres) => {
    console.log("user has entered for the first time", newPres)
  } else {
    console.log("user additional presence", newPres)

I’ve tried async, and defining presence logic in the join callback, and while I can log presence itself and see the functions, the event listeners never fire