phoenix_live_view icon indicating copy to clipboard operation
phoenix_live_view copied to clipboard

Upload fails with `** (KeyError) key nil not found in: %{"phx-Fxi0xChrt8AgvA8o" => :uploaded_files}`

Open lessless opened this issue 3 years ago • 1 comments

Environment

  • Elixir version (elixir -v): Elixir 1.14.0 (compiled with Erlang/OTP 25)
  • Phoenix version (mix deps): 1.6.12 (phoenix) 2d6cf558
  • Phoenix LiveView version (mix deps): 0.17.12 (phoenix_live_view) af6dd5e0
  • Operating system: macOS
  • Browsers you attempted to reproduce this bug on (the more, the merrier): Chrome
  • Does the problem persist after removing "assets/node_modules" and trying again? Yes/no: yes

Actual behaviour

data-phx-upload-ref is being removed on a live_file_input that causes subsequent upload to fail with

[error] GenServer #PID<0.1996.0> terminating
** (KeyError) key nil not found in: %{"phx-Fxi0xChrt8AgvA8o" => :uploaded_files}
    :erlang.map_get(nil, %{"phx-Fxi0xChrt8AgvA8o" => :uploaded_files})
    (phoenix_live_view 0.18.0) lib/phoenix_live_view/upload.ex:191: Phoenix.LiveView.Upload.get_upload_by_ref!/2
    (phoenix_live_view 0.18.0) lib/phoenix_live_view/upload.ex:125: Phoenix.LiveView.Upload.update_progress/4
    (phoenix_live_view 0.18.0) lib/phoenix_live_view/channel.ex:146: anonymous fn/4 in Phoenix.LiveView.Channel.handle_info/2
    (phoenix_live_view 0.18.0) lib/phoenix_live_view/diff.ex:206: Phoenix.LiveView.Diff.write_component/4
    (phoenix_live_view 0.18.0) lib/phoenix_live_view/channel.ex:1214: Phoenix.LiveView.Channel.write_socket/4
    (phoenix_live_view 0.18.0) lib/phoenix_live_view/channel.ex:144: Phoenix.LiveView.Channel.handle_info/2
    (stdlib 3.16) gen_server.erl:695: :gen_server.try_dispatch/4
    (stdlib 3.16) gen_server.erl:771: :gen_server.handle_msg/6
    (stdlib 3.16) proc_lib.erl:236: :proc_lib.wake_up/3
Last message: %Phoenix.Socket.Message{event: "progress", join_ref: "4", payload: %{"cid" => 6, "entry_ref" => "5", "event" => nil, "progress" => 100, "ref" => nil}, ref: "12", topic: "lv:phx-Fxi0xBoNetjk9A8I"}

Expected behaviour

Uploads do not fail

Details

We are using LiveView to build a potentially unbound file upload from a browser directly to a cloud, where files should be uploaded as soon as a user selects/drags them onto the upload zone:

def mount(socket) do
    {:ok,
     socket
     |> allow_upload(:uploaded_files,
       accept: :any,
       max_entries: 100,
       auto_upload: true,
       progress: &handle_progress/3,
       external: &presign_upload/2
     )
  end

Since failed uploads blocks subsequent uploads, we are cleaning up the entries in the handle_progress callback:

def handle_progress(:uploaded_files, %{done?: true} = finished_entry, socket) do
  consumed_entry =
    consume_uploaded_entry(socket, finished_entry, fn _ ->
      {:ok, finished_entry}
    end)

  {:noreply, socket}
end

def handle_progress(:uploaded_files, entry_in_progress, socket) do
  socket =
    case upload_errors(socket.assigns.uploads.uploaded_files, entry_in_progress) do
      [] ->
        socket

      errors ->       
        cancel_upload(socket, :uploaded_files, entry_in_progress.ref)
    end

  {:noreply, socket}
end

Our brief investigation showed that calling entry.error inside an external uploader removes the data-phx-upload-ref on the live_file_input through UploadEntry.error -> LiveUploader.clearFiles(this.fileEl). See https://github.com/phoenixframework/phoenix_live_view/blob/master/assets/js/phoenix_live_view/upload_entry.js#L74 and https://github.com/phoenixframework/phoenix_live_view/blob/v0.17.12/assets/js/phoenix_live_view/live_uploader.js#L59

let Uploaders = {};

Uploaders.GCloud = function (entries, onViewError) {
  entries.forEach((entry) => {
    const { url } = entry.meta;
    const file = entry.file;

    return fetch(url, {
      method: 'PUT',
      body: file,
    })
      .then((response) => {
        if (response.status != 200 || file.name.includes('reproduce-the-problem.zip')) {
          entry.error('upload failed');
        } else {
          entry.progress(100);
        }
      })
      .catch((error) => {;
        console.log(error);
        entry.error('upload failed');
      });
  });
};

If anyone could explain when and why that attribute should be cleared we will be happy to submit a patch.

lessless avatar Sep 27 '22 14:09 lessless

Note to self: it takes a few extra steps to find the history here since it occurred before the big javascript refactor– we originally began removing the upload ref on error as a remedy to the following:

  • #1448

mcrumm avatar Oct 02 '22 21:10 mcrumm