Unable to run update in LiveComponent

Hi,

I’m trying to build a LiveComponent, which provides a drop-down where users can select different items.
I could use some help getting update() to run so that I can assign some data.
Following the tutorial at Fly.io, it looks like update() only needs params and socket?
How should I craft my code to assign data when the component loads?
In my attempt I get this error:

# Returns CompileError: cannot declare attributes for function update/2. 
# Components must be functions with arity 1

This is how I use the component from the main view:

      <.live_component
        module={ComponentSelector}
        name="Camera"
        context="camera"
        coming_from="dsp"
        form={@form}
        list_for_selector={Cameras.list_approved_cameras()}
        id="dsp"

The LiveComponent itself:

defmodule MyAppWeb.Components.ComponentSelector do
  use MyAppWeb, :live_component
  import Phoenix.HTML.Form
  alias MyApp.Cameras

  attr :name, :string, required: true
  attr :list_for_selector, :list, required: true
  attr :form, :map, required: true
  attr :context, :string, required: true


# Returns CompileError: cannot declare attributes for function update/2. 
# Components must be functions with arity 1
  def update(_params, socket) do
    IO.puts("\n\n ComponentSelector update\n\n ")
    # ^ This is never called
    {:ok, assign(socket, :selected_components, [])}
end



  def render(assigns) do
    key = @name

    ~H"""
    <div class="component-selector">
      <h2><%= @name %></h2>

      <select phx-change="component_selected_from_dropdown" name="component_selected" 
       phx-target={@myself} >
        <%= options_for_select(@list_for_selector, 0) %>
      </select>

      <button
        form="ignore-me"
        phx-click="add_component"
        phx-target={@myself}
        value={"#{String.downcase(@name)}" }
        class="ml-2"
      >
        Add
      </button>
    </div>
    """
  end
 def handle_event("add_component", %{"value" => context}, socket) do
    component_id = socket.assigns.component_id_selected

    if component_id == nil do
      {:noreply, socket}
    else
      IO.puts "to be implemented"

      component =
        case context do
          "display" ->
            display = Components.get_display!(component_id)
          "camera" ->
            Cameras.get_camera!(component_id)
        end

      components = socket.assigns.selected_components
      socket = assign(socket, :selected_components, [component | components])

      {:noreply, socket}
    end
1 Like

update(assigns, socket) would be the correct signature. In the assigns you may find your params, so, if you name it params, it would be ok

from the docs regarding lifecycle:
mount(socket) -> update(assigns, socket) -> render(assigns)

I’m able to run update() when I comment out attr: name and the rest.
But if I remove that code, then I’m not able to pass those attributes from main parent view to the LiveComponent.
E.g:

      <.live_component
        module={ComponentSelector}
        name="Camera"

And

defmodule MyAppWeb.Components.ComponentSelector do
  use MyAppWeb, :live_component
  import Phoenix.HTML.Form
  alias MyApp.Cameras

  attr :name, :string, required: true

So, how can I still keep the attrs interface and do a assign(socket, :selected_components, [])} when the component loads?

I guess that was what the error message was trying to tell me:

Returns CompileError: cannot declare attributes for function update/2.
Components must be functions with arity 1

Unfortunately, you can only call attr and slot with function components. They don’t work in live components.

I grokked it! :blush:

The data passed in as attributes to .live_component was in need available in my params.
It was just that the params were so big that I failed to do proper pattern matching on it.
I solved it by just extracting the values that I needed, like this:

 def update(params, socket) do
    form=params.form
    id=params.id
    name=params.name
    selectable_options=params.selectable_options
    coming_from=params.coming_from

    socket =
      socket
      |> assign(:id, id)
      |> assign(:selectable_options, selectable_options)
      |> assign(:form, form)
      |> assign(:name, name)

    {:ok, socket}
  end

Thanks for helping out, guys! :smile: