Upload fails with `** (KeyError) key nil not found in: %{"phx-Fxi0xChrt8AgvA8o" => :uploaded_files}`
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.
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