Push_event from update() function, frontend seems to not receive events?

Goal

I am currently trying to plot multiple markers on a map in a phoenix live view.

One marker is meant to always show the latest known position of an object.

Additionally, the user shall be able to set the timeframe to be displayed and the map will show markers for all known positions during the selected timeframe.

What works?

  1. The Map is rendered as a live component.
  2. I have hooks to add, move or remove markers and pan to other locations on the map.
  3. The marker meant to display the live position works well. It is initially created and then moved whenever a new position update is received.

However, the points listed under 2. seem to only work in some occasions.

What does not work?

The frontend seems to not receive anything if push_event() is called from phoenix update() function

During mount() I can use push_event() to initiate the view (pan to some location) and add initial markers. I can update the initial markers from a handle_info(). This is used to move a devices marker live to the last known position and works well.

To display a timeseres, my idea was to simply pass a list of positions to the map component and clear/add the markers from the components update() function. So the user would select a timeframe and the resulting positions are passed to the map component in the assigns. However, no events arrive at the frontend, even tho the backend seems to do exactly what I want (based on some printouts).

Copying the exact same code from the mount() function to the update() function also does not work (I tested it with a hardcoded list of positions that are pushed to the frontend).

Questions

Does it make a difference if I call push_event() from update()?
What could be reasons that the frontend does not receive the events, if it works from mount() and handle_info()?

can you show us the code that’s causing the issue? I have my suspicion from your description but let’s see what you have there. Just the update function should do.

Sure, I stripped it down to a minimal example. Hope I didn’t forget anything.
I added more than just the update function for people who end up here in future :slight_smile:

Javascript hook in leaflet_map.hooks.js:

let Map = {
  mounted(){
    const markers = {}
    const map = L.map('mapid').setView([51.505, -0.09], 14)

    ... # functions to create the tile layers etc

    this.handleEvent("add_marker", ({lat, lon}) => {
        const marker = L.marker(L.latLng(lat, lon))
        marker.addTo(map)
    })
  }
}

export {Map}

Map LiveComponent in leaflet_map.ex:

defmodule LeafletMap do
   use Surface.Component 
   prop positions, :list, default: []

  def update(assigns, socket) do
    socket = Map.put(socket, :assigns, assigns)

    socket =
      assigns.positions
      |> Enum.reduce(socket, fn {lat, lon}, acc_socket ->
        acc_socket
        |> push_event("add_marker", %{lat: lat, lon: lon})
      end)

     IO.inspect socket.private # <-- lists the pushed events as expected

    {:ok, socket}
  end

   def render(assigns) do
      ~F"""
      <div class="ui segment" phx-update="ignore">
        <div id="mapid" style={"height: #{@height};"} :hook="Map"></div>
      </div>
      """
    end
  end
end

Map LiveView in plot_positions.ex_:

defmodule PlotPositions do
  use Surface.LiveView
  def mount(_params, _session, socket) do
    positions = #.. read from file or DB

    {:ok, socket |> assign(positions: positions)}
  end

  def render(assigns) do
    ~F"""
      <LeafletMap
        positions={@positions}
      />
    """
  end
end

I don’t have an idea as to why the client doesn’t receive the pushed events from the server, but I did notice some things.

I believe the hook’s updated callback isn’t called because you use a stateless component, which should be re-rendered each time the parent template changes:

A stateless component is always mounted, updated, and rendered whenever the parent template changes. That’s why they are stateless: no state is kept after the component.

Even if you did used a stateful component instead, you would still see the same behaviour due to the way you assign the assigns and the fact that nothing in the hook’s container actually changes.

I wouldn’t assign the assigns like this as the engine won’t know if any of the assigns changed since you’ve manually changed it. Better use socket = assign(socket, assigns)

Thanks, these are all good points too. I have looked at the code @larshei posted but I don’t see anything wrong with it on Elixir side either…

Good point!

I ended up having a custom update_map() function that encapsulates all the push_event() calls and other update logic. And I simply call it from the live view when the map needs to be updated (<- happens only as a result of user input, so that is easy to do).

Not beautiful, but its simple and works for now.