Phoenix.Router.NoRouteError on existing file


I am trying to create a crud Liveview 0.19.5 program created from the Phoenix 1.7.7 template. Most code was generated by phoenix and ecto from the command line, so it is the default CRUD form with Master/Detail view that I am using.

The CRUD Master/Detail form worked well until I added a file upload to the detail form. The uploaded image is uploaded, the problem is that I get a Phoenix.Router.NoRouteError in the master form when trying to display a thumbnail of the uploaded image. So after saving, the detail form (livecomponent) is hidden and I get this:

The two broken images come from the form:
The form is (generated from command line, adapted to show the image):

  row_click={fn {_id, tag} -> JS.navigate(~p"/tags/#{tag}") end}
  <:col :let={{_id, tag}} label="Tag"><%= tag.tag_id %></:col>
  <:col :let={{_id, tag}} label="Image url"><img src={tag.image_url} width="50" /></:col>
  <:col :let={{_id, tag}} label="Name"><%= %></:col>
  <:col :let={{_id, tag}} label="Volume"><%= tag.volume %></:col>
  <:action :let={{_id, tag}}>
    <div class="sr-only">
      <.link navigate={~p"/tags/#{tag}"}>Show</.link>
    <.link patch={~p"/tags/#{tag}/edit"}>Edit</.link>
  <:action :let={{id, tag}}>
      phx-click={JS.push("delete", value: %{id:}) |> hide("##{id}")}
      data-confirm="Are you sure?"

and also from a “non stream” source (the first broken image displayed) , as I am not familiar with streams yet:

<%= for tag <- @tags do %>
  <img src={tag.image_url} width="50" />
<% end %>

The code used to upload the image in the detail form is:

index.ex (liveview)

  def handle_event("save", %{"tag" => tag_params}, socket) do
    entry_urls =
      consume_uploaded_entries(socket, :image, fn %{path: path}, _entry ->
        dest = Path.join("priv/static/uploads", Path.basename(path))
        File.cp!(path, dest)
        {:ok, Routes.static_path(socket, "/uploads/#{Path.basename(dest)}")}
    tag_params =
      case entry_urls do
        [url] -> tag_params |> Map.put("image_url", url)
        _ -> tag_params
    save_tag(socket, socket.assigns.action, tag_params)

Save tag saves the data and notifies the parent liveview:
form_component.ex (livecomponent):

  defp save_tag(socket, :edit, tag_params) do
    case Player.update_tag(socket.assigns.tag, tag_params) do
      {:ok, tag} ->
        notify_parent({:saved, tag})

         |> put_flash(:info, "Tag updated successfully")
         |> push_patch(to: socket.assigns.patch)}
#         |> push_navigate(to: "/tags", replace: true)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}

Notify parent is an helper function that is generated:

defp notify_parent(msg), do: send(self(), {__MODULE__, msg})

The code called is then

  @impl true
  def handle_info({RfidPlayerWeb.TagLive.FormComponent, {:saved, tag}}, socket) do
    socket  =
      socket|> assign(:tags, Player.list_tags())
    {:noreply, stream_insert(socket, :tags, tag)}

In my case, data is updated correctly, and the image exists on the server. Actually, the image is displayed if I use F5 to refresh:

Though, if I edit, without changing the image, I get a different behaviour from the “streams” version and this one:

<%= for tag <- @tags do %>
  <img src={tag.image_url} width="50" />
<% end %>

After editing (i.e. going through the “save” event above and the liveview notification) the image will be displayed. The table using streams, will still show the broken image, unless I refresh the page with F5.

I don’t understand what is going wrong. In courses I have dealing with uploads, PubSub is used to update the liveview, so I can’t compare with my own code.

From what I see, the image is there but not found by the router in some situations. It is not a matter of time to copy the file to the uploads folder: I added a timer and the result is the same, moreover if I edit the record again, the image is displayed by the “non stream” version of the code.
I also tried push_navigate but the result is the same. I don’t understand what is happening here.

Sorry for the long post, could not find a way to explain everthing this in a more compact way.