Maxim-Filimonov

Maxim-Filimonov

Hi,
Coming from React I’ve been a bit struggling to understand how to structure and handle live component events.
To give a specific example I have two live components.

  1. FileUpload live component which presents a dialog for the user to upload files
  2. Files live component which wraps FileUpload and converts file to appropriate structure to attach to the parent entity.
    What I’m trying to do seems to be relatively simple - I want the FileUpload component to call a handler passed to it with a list of uploaded files. That handler is in the Files component.
    I’m aware of the fact that I can use send and handle_info in the main parent LV. But in this scenario, I don’t want LV to handle this communication and let those components handle it.
    I am looking at it in the wrong way?

Showing Posts 1 to 10

joshdcuneo

joshdcuneo

Could you share what you have for those two components currently?

I’ve found myself in the same position a few times coming from React and my general inclination at the moment is to use functions for markup and LiveView components for reusing behaviours. I tend to have fewer components in LiveView than I would naturally have for the same UI in React.

Maxim-Filimonov

Maxim-Filimonov OP

We are using the surface UI and it’s pretty much the same deal. Just slightly different syntax for templates.

Files component:

 def render(assigns) do
    ~H"""
    <div>
      <FileUpload
        id="file-upload"
        accept={{ :any }}
        max_entries=3
        upload_handler={{ %{target: __MODULE__, id: @files_id}  }}
      />
      <span :for={{ attachment <- @attachments }}>{{ attachment.file.name }}</span>
    </div>
    """
  end
 def handle_uploaded_files(files_id, files) do
    send_update(__MODULE__, id: files_id, uploaded_files: files)
  end

def update(assigns, socket) do
    if assigns[:uploaded_files] do
      uploaded_attachments =
        Enum.map(
          assigns[:uploaded_files],
          fn f -> attach_to_owner(f, owner) end
        )

      socket =
        update(socket, :attachments, fn attachments -> attachments ++ uploaded_attachments end)

      {:ok, socket}
    else
      attachments = get_files_for(owner)
      socket = socket |> assign(assigns) |> assign(attachments: attachments)
      {:ok, socket}
    end
  end

FileUpload component:

def render(assigns) do
    ~H"""
    <Form submit="save" change="validate" for={{ :upload }}>
      <LiveFileInput upload={{ @uploads.file }} />
      <Form.Submit>Upload</Form.Submit>
      <p class="alert-error" :for={{ {_ref, error} <- @uploads.file.errors }}>
        {{ Phoenix.Naming.humanize(error) }}
      </p>
      <For each={{ entry <- @uploads.file.entries }}>
        <div class="flex flex-col w/2">
          {{ live_img_preview(entry, height: 80, class: "flex") }}
          <progress max="100" value={{ entry.progress }} />
          <button :on-click="cancel-upload" phx-value-ref={{ entry.ref }}>
            Cancel
          </button>
        </div>
      </For>
      <span class="alert-info" if={{ length(@uploaded_files) }}>{{ length(@uploaded_files) }} {{ inflect("files", length(@uploaded_files)) }} uploaded.</span>
    </Form>
    """
  end

def handle_event("save", _params, socket) do
    # Entries are already consumed by S3 client uploader need to call this to trigger cleanup.
    files =
      consume_uploaded_entries(socket, :file, fn meta, entry ->

        Path.join(
          s3_host(socket.assigns.bucket, region),
          s3_key(entry, socket.assigns.path_prefix)
        )
      end)

    %{target: target, id: id} = socket.assigns.upload_handler

    target.handle_uploaded_files(id, files)

    {:noreply, assign(socket, :uploaded_files, files)}
  end

What I did for now is pushed the target module as a property to children component and drilled the id of it. It feels very hacky to me and I’m not even sure how to unit test this at the moment.

joshdcuneo

joshdcuneo

I’d love to hear what patterns people are using for components like this. I would probably just have one component personally or use send(self(), message) to handle it.

APB9785

APB9785

Creator of ECSx

When I last implemented live uploads (for user avatar images), I put the allow_upload/3 in the main LiveView (mount), then pass the @uploads assign to a stateful LiveComponent, where the user does the upload with live_file_input/2. The input is inside a form with phx_target: @myself so the Component will handle the form submission event. When it’s finished, it emits a PubSub message so the image link will be available globally.

Maxim-Filimonov

Maxim-Filimonov OP

So you use PubSub to communicate between components ?
I do like this way, though I have been told that it can have severe performance impact as message is being broadcasted to all connected LV clients, have you noticed the impact ?

APB9785

APB9785

Creator of ECSx

I haven’t noticed any performance issues in testing. And I use PubSub for many things, not just file uploads; also live updates for user profiles and comments and such.

If I didn’t need to keep the other users up-to-date, I would just use send(self(), message) (the parent LV is always the source of truth). But PubSub covers both bases.

cmo

cmo

Have you tried handling the save event in the patent and passing the event down to FilesUpload? I might be missing something but it seems odd to pass the parents module down so you can tell it to update itself with a callback.

The handle_uploaded_files could then be moved to the child so the parent can call that to update the child.

APB9785

APB9785

Creator of ECSx

Sure, you could handle it in the parent LV. I like to use Components as much as possible for separation of concerns and to reduce the amount of code in the parent LiveView module.

it seems odd to pass the parents module down so you can tell it to update itself with a callback.

Remember that I am using PubSub to not just update the parent LV, but ALL liveviews globally. So I am sending out a message either way; it doesn’t really matter whether it’s the LV or the LC which sends the message.

Maxim-Filimonov

Maxim-Filimonov OP

The problem specific to our scenario is that the Files component is actually used in several different live views. Which means if parent LV is responsible for handling the comms we would need to copy/paste the same logic.

APB9785

APB9785

Creator of ECSx

Have you seen Phoenix.LiveComponent - Targeting Component Events? If your components are the source of truth in your app, and you don’t want to involve the parent LV with handling the event, it looks like you can target another component and even multiple components by passing their DOM id(s) to phx-target.

I’m not too familiar with this because in my apps I prefer the parent LV to be the source of truth for almost everything. But maybe it could work for your use case?

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews