Map Attr in LiveView with Stream

I have a component

  attr :capability_id, :string, required: true
  attr :device_id, :string
  attr :name, :string, required: true
  attr :value, :map
  attr :type, :string, required: true
  attr :state, :string
  attr :unit, :string
  attr :parent, :string
  def three_axis_sensor(assigns) do
    ~H"""
     <div class={"flex w-full"} >
        <div class="flex justify-start w-1/2">
          <%= @name %>
        </div>
        <div class="flex justify-end w-1/2 pl-34">
          <span id={@capability_id<>"-three-axis"}><%= @value["x"] %>, <%= @value["y"] %>, <%= @value["z"] %></span>
          <.link phx-click={JS.push("delete_cap", value: %{id: @capability_id})} data-confirm="Are you sure?" class={"pl-3"}   phx-target={ LorpWeb.CapabilityComponent }>
             <.icon name="hero-trash" class="h-4 w-4"/>
          </.link>
        </div>
      </div>
    """
  end

When the data updates, the new map is sent via the stream. I can inspect it. However, the values are removed and replaced with nothing. My non-map attr are working and are configured in a very similar way. There are no error messages and nothing to debug in the console.

You’re putting value on the socket with stream? I never thought to try that with a map. Wonder if it works… You don’t have phx-update="stream" on the parent so it’s not gonna work without that.

1 Like

phx-update is in the parent above the component. Other components like this work just fine. Just this one with a map value.

      <div id={@id} phx-update="stream">
        <div :for={{id, capability} <- @capabilities} id={id} class="px-2">
          <.property_item capability={capability} device={@device} parent={id} id={"child_#{id}"}/>
        </div>
      </div>

Can you show how you’re calling <.three_axis_sensor value={...}> and how you’re updating the @value map and the stream on the server side?

defmodule CapabilityComponent do
###### The Live Component ########

  @impl true
  def render(assigns) do
    ~H"""
      <div id={"caps-#{@device_id}"}>
        <.property_list id={"list-"<>@device.id} device={@device} capabilities={@streams.capabilities} />
      </div>
    """
  end

  @impl true
  def update(%{device: device} = _assigns, socket) do
    {:ok,
      socket
      |> assign(:device_id, device.id)
      |> assign(:device, device)
      |> stream(:capabilities,  device.capabilities)
    }
  end

  def update(%{event: data}, socket) do
    {:ok, stream_insert(socket, :capabilities, data,  at: -1)}
  end
end

The property list renders the different cards based on certain attributes. All of the cards are updating excepting the one that takes a map as an attr. Three axis is just one kind of attribute that can be rendered.

It’s a bit tricky when you show small sections of the code piecemeal and don’t include the code in question. It might be worth creating a small example project and posting an issue on the repo.

1 Like

Can i get the documentation link for what does strem and liveview means here?

I resolved it by not using maps in my updates. But here is all of the relevant code.

There is a live component

defmodule CapabilityComponent do
  use App, :live_component
  import App.PropertiesCard
  alias App.CapabilityManager


  @impl true
  def render(assigns) do
    ~H"""
      <div id={"caps-#{@device_id}"}>
        <.property_list id={"list-"<>@device.id} device={@device} capabilities={@streams.capabilities} />
      </div>
    """
  end

  @impl true
  def update(%{device: device} = _assigns, socket) do
    {:ok,
      socket
      |> assign(:device_id, device.id)
      |> assign(:device, device)
      |> stream(:capabilities,  device.capabilities)
    }
  end

  def update(%{event: data}, socket) do
    {:ok, stream_insert(socket, :capabilities, data,  at: -1)}
  end

  @impl true
  def handle_event("delete_cap", params, socket) do
    cap = CapabilityManager.get_capability!(params["id"])
    {:ok, _} = CapabilityManager.delete_capability(cap)

    {:noreply, stream_delete(socket, :capabilities, cap)}
  end


end

Each live component renders a list of properties

attr :capabilities, :list
  attr :device, Lorp.Core.DeviceManager.Device
  attr :id, :any
  attr :div_id, :any, default: nil
  slot :content
  def property_list(assigns) do
    ~H"""
      <div id={@id} phx-update="stream">
        <div :for={{id, capability} <- @capabilities} id={id} class="px-2">
          <.property_item capability={capability} device={@device} parent={id} id={"child_#{id}"}/>
        </div>
      </div>
    """
  end

Each item could be one of many given types. Here is the one relevant to three axis

  attr :parent, :string
  attr :device, Lorp.Core.DeviceManager.Device
  attr :capability, Lorp.CapabilityManager.Capability
  attr :capability_id, :string
  attr :id, :string
  def property_item(assigns) do
    ~H"""
         <.three_axis_sensor
      :if={@capability.type == "three_axis_sensor"}
      capability_id={@capability.id}
      name={"Three Axis"}
      type={"three_axis_sensor"}
      parent={@parent}
      device_id={@device.id}
      x_value={@capability.state.value_x}
      y_value={@capability.state.value_y}
      z_value={@capability.state.value_z}
    />
  """
end

Here is the three axis card


  attr :capability_id, :string, required: true
  attr :device_id, :string
  attr :name, :string, required: true
  attr :value, :map
  attr :type, :string, required: true
  attr :state, :string
  attr :unit, :string
  attr :parent, :string
  attr :x_value, :any
  attr :y_value, :any
  attr :z_value, :any
  def three_axis_sensor(assigns) do
    ~H"""
     <div class={"flex w-full"} >
        <div class="flex justify-start w-1/2">
          <%= @name %>
        </div>
        <div class="flex justify-end w-1/2 pl-34">
          <span id={@type<>"-"<>@capability_id}><%= @x_value %>, <%=  @y_value  %>, <%=  @z_value  %></span>
          <.link phx-click={JS.push("delete_cap", value: %{id: @capability_id})} data-confirm="Are you sure?" class={"pl-3"}   phx-target={ LorpWeb.CapabilityComponent }>
             <.icon name="hero-trash" class="h-4 w-4"/>
          </.link>
        </div>
      </div>
    """
  end