Reset live_file_input after file has been uploaded

I have a LiveView file upload form. It has a single input to choose file. Everything works, file is being uploaded, but after it’s been uploaded the input still looks like has the file selected.

This is my form:

    @impl true                                                                                                                                                           
    def render(assigns) do                                                                                                                                               
      ~L"""                                                                                                                                                              
      <form id="<% @id %>-upload-form"                                                                                                                                   
            class="upload-component"                                                                                                                                     
            action="#"                                                                                                                                                   
            phx-drop-target="<%= @uploads.file.ref %>"                                                                                                                   
            phx-submit="save"                                                                                                                                            
            phx-change="validate"                                                                                                                                        
            phx-target="<%= @myself %>">                                                                                                                                 
                                                                                                                                                                         
        <%= live_file_input @uploads.file %>                                                                                                                             
                                                                                                                                                                         
        <button type="submit">Upload</button>                                                                                                                            
      </form>                                                                                                                                                            
      """                                                                                                                                                                
    end 

And, after I choose and upload file and hit “Upload”, it gets properly uplkoaded to backend but form input doesn’t re-set, and stays looking like this:
wat

Three ways I found how to do it are less than ideal. I can do it either:

  1. By installing a Hook on the form, which in turns does this.el.reset() when receives a message pushed from the liveview that upload finished.

  2. By removing the file input from the liveview for 0.1second and then rendering it again, which obviously blinks to the user because it’s causing this re-rendering.

  3. By installing a hook that does input.value = "" when receives messgae pushed from liveview that upload finished, similar to 1).

Is there a better way that I am missing?

Method 2) is probably best from the above and looks like this:

  defmodule Admin.Live.FileUploadComponent do                                                                                                                       
    use Admin, :live_dashboard_component                                                                                                                                 
                                                                                                                                                                         
    @max_file_size 1024 * 1024 * 100; # 100MB                                                                                                                            
                                                                                                                                                                         
    @impl true                                                                                                                                                           
    def mount(socket) do                                                                                                                                                 
      {:ok,                                                                                                                                                              
        socket                                                                                                                                                           
        |> allow_upload(:file, accept: ~w(.xlsx), max_file_size: @max_file_size, auto_upload: false)                                                                     
        |> assign(:upload_just_finished, false)                                                                                                                          
      }                                                                                                                                                                  
    end                                                                                                                                                                  
                                                                                                                                                                         
    @impl true                                                                                                                                                           
    def render(assigns) do                                                                                                                                               
      ~L"""                                                                                                                                                              
      <form id="<% @id %>-upload-form"                                                                                                                                   
            class="upload-component"                                                                                                                                     
            action="#"                                                                                                                                                   
            phx-drop-target="<%= @uploads.file.ref %>"                                                                                                                   
            phx-submit="save"                                                                                                                                            
            phx-change="validate"                                                                                                                                        
            phx-target="<%= @myself %>">                                                                                                                                 
                                                                                                                                                                         
        <%= unless @upload_just_finished do %>                                                                                                                           
          <%= live_file_input @uploads.file, value: "" %>                                                                                                                
          <button type="submit">Upload</button>                                                                                                                          
        <% end %>                                                                                                                                                        
      </form>                                                                                                                                                            
      """                                                                                                                                                                
    end                                                                                                                                                                  
                                                                                                                                                                         
    @impl true                                                                                                                                                           
    def handle_event("validate", _, socket) do                                                                                                                           
      {:noreply, socket}                                                                                                                                                 
    end                                                                                                                                                                  
                                                                                                                                                                         
    @impl Phoenix.LiveView                                                                                                                                               
    def handle_event("save", _params, socket) do                                                                                                                         
      uploaded_files =                                                                                                                                                   
        consume_uploaded_entries(socket, :file, fn %{path: path} = meta, _entry ->                                                                                       
          meta                                                                                                                                                           
        end)                                                                                                                                                             
                                                                                                                                                                         
      send_update_after(self(), __MODULE__, %{id: socket.assigns.id, upload_just_finished: false}, 1)                                                                    
                                                                                                                                                                         
      {:noreply, socket |> assign(:upload_just_finished, true)}                                                                                                          
    end                                                                                                                                                                  
  end     

But you can see the blinking…

I have started playing with live_file_upload too but always used it in a modal which closes upon success. So never experienced this problem.

Are you able to set @uploads.file on the socket directly?

Can you share a gif, video of this blinking? It is usually instantaneous unless you have some CSS, browser plugin to cause jitter, blinking

You just need to assign for an empty struct %Desk{} with the changeset after you save.
In my sample of the course liveview by PragProg I do this:


   case Desks.create_desk(desk, params, &consume_photos(socket, &1)) do
      {:ok, _desk} ->
        changeset = Desks.change_desk(%Desk{})
        {:noreply, assign(socket, changeset: changeset)}
     
      {:error, %Ecto.Changeset{} -> ....

he isn’t using the form_for or changeset in his code, plain html form.

Yeah but probably if I replace my @uploads with the one that has nullified “file” on it it may work… trying it now.

yes, I see now.
I recomend use this form_for like this:
<%= f = form_for @changeset, "#", ...

Well looking at LiveView documentation it appears after I use consume_uploaded_entries/3 the entries I just consumed should have been automatically removed from uploads.

It appears they are not removed for me.

The documentation states:

Raises when there are still entries in progress. Typically called when submitting a form to handle the uploaded entries alongside the form data. For form submissions, it is guaranteed that all entries have completed before the submit event is invoked. Once entries are consumed, they are removed from the upload.

from:
https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#consume_uploaded_entries/3

I can’t quickly test from the modal component into LV directly.

on your console enable debug and check the diffs as you do the operations liveSocket.enableDebug()

if you share the LV code fully, I could test locally here also.

I’ll get an example repo up somewhere. I would’t be surprised if isolating that from rest of the app fixed the issue, had this before. But either way good suggestion to try doing it, it may help pin point the issue.

@hubertlepicki what browser are you using?

I have a repo with several upload demos if you’d like to fork it an add a new one - GitHub - mcrumm/live_upload_example: Demonstrating file uploads with Phoenix LiveView

I just tested my Basic Uploads demo in Chrome, Firefox, and Safari and as soon as I select a file, I see the entry in the pending uploads list, but the input itself resets immediately.

If you can isolate the issue in a standalone example I would love to take a closer look!

1 Like

Looks like I don’t have to create anything then. On your demo → Component Demo in Chrome 90 I can see the same issue. You can see on the video below that the file name doesn’t disappear from the file input after I submit it.

I think this may be component-specific issue where clearing the entries on the channel doesn’t get propageted to the component…
liveupload

2 Likes

Something that might work is to assign id for file input. Then when upload is done change id. Example by increasing a counter that is added to id.

Yep, that’s a bug :slight_smile: Would you please open an issue on the GitHub repo?

The id attribute is controlled by the live_file_input/3 helper and will raise an ArgumentError if you try to override it.

Did you have a chance to try the workaround?

yep will do, just make myself a cup of decaf and will make better video. Elixirforum allows for 400kb uploads which is super annoying to make video gif like this that actually shows the issue. @AstonJ would you consider bumping it up to several megabytes or not possible?

1 Like

Upload to Imgur and paste the link to the forum. It will play native

1 Like

No, the one with ID change? No. I pushed the code with blinking/disappearing/reappearing input and it passed through the QA no problem but if I leave it like that I won’t be able to sleep. So, a proper fix in LiveView will be best long term.

This one (till you get a fix in LV)

1 Like

No, this doesn’t work unforunately either.