I can’t figure out why when deleting an article from the list:
defmodule MyAppWeb.ArticleLive.Index
...
on_mount({MyAppWeb.UserAuth, :mount_current_user})
@impl true
def mount(_params, _session, socket) do
{:ok, stream(socket, :articles, Articles.list_articles()}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
article = Articles.get_article!(id)
current_user_id = socket.assigns.current_user.id
if article.author_id != current_user_id do
raise """
An unauthorized author tried to delete article #{article.id} by author #{current_user_id}
"""
end
{:ok, _} = Articles.delete_article(article)
{:noreply, stream_delete(socket, :articles, article)}
end
Here is how the list of articles is displayed in index.html.heex
:
...
<%= if Enum.count(@streams.articles) == 0 do %>
<div class="mt-20">
<MyAppWeb.Component.EmptyState.show text="You have no articles yet." image="wall-post.svg" />
</div>
<% else %>
<.table
id="articles"
rows={@streams.articles}
row_click={fn {_id, article} -> JS.navigate(~p"/articles/#{article}") end}
>
<:col :let={{_id, article}} label="Title"><%= article.title %></:col>
<:col :let={{_id, article}} label="Content">
<%= String.slice(article.content, 0, 200) %>
</:col>
<:action :let={{_id, article}}>
<div class="sr-only">
<.link navigate={~p"/articles/#{article}"}>Show</.link>
</div>
<.link patch={~p"/articles/#{article}/edit"}>Edit</.link>
</:action>
<:action :let={{id, article}}>
<.link
phx-click={JS.push("delete", value: %{id: article.id}) |> hide("##{id}")}
data-confirm="Are you sure?"
>
Delete
</.link>
</:action>
</.table>
<% end %>
...
When I click on the link to delete an article and confirm the deletion, no more articles are displayed on the page. But if I refresh the page, I have the list of articles without the deleted one.
When comparing the above content with another basic Phoenix app, everything seems to be correct.
Any ideas?
It’s the Enum.count(@streams.articles)
. Streams are not held on the server by design so that key is emptied when the data is sent to the client.
Hmm, removing the ìf` clause fixed the problem. Here is how the empty state component looks like:
defmodule MyAppWeb.Component.EmptyState do
use MyAppWeb, :html
attr(:text, :string, required: true)
attr(:image, :string, required: true)
def show(assigns) do
~H"""
<div>
<h2 class="text-2xl font-semibold tracking-tight sm:text-center sm:text-lg">
<%= @text %>
</h2>
<div class="w-full max-w-xs mx-auto mt-10">
<img
src={~p"/images/#{@image}"}
alt=""
class="w-full rounded-xl ring-1 ring-gray-400/10 max-w-none md:-ml-4 lg:-ml-0"
/>
</div>
</div>
"""
end
end
So this kind of check <%= if Enum.count(@streams.articles) == 0 do %>
will never work then? If so, what would be better - remove it completely or replace it with something other? More of that, we assign rows={@streams.articles}
later on to the rows. How is it possible that calling to Enum.count
will fail then?
Correct, it will never work. It’s not something I’ve dealt with before but I’m pretty sure there was some recent discussion about this. I’m on my phone otherwise I’d try and find it. Would be regarding “message when stream empty” or something like that.
The former works because of using phx-update="stream"
to tell the client to not remove things on update if they’re not present in @stream.articles
, but only when the stream includes an explicit deletion. That allows for dropping any data in @stream.articles
after each render. That’s the whole reason why streams exist in the first place - to not need to retain the collection on the server side.
2 Likes
OK, thank you very much. I’ll try to search a thread for a message when stream empty or smth like that :).
I’m on a laptop now I’m sure you found it as I searched that and it was the first result but here it is!
1 Like
Actually I have the same request as your question. Does anyone know how to remove all the items from the liveview stream? I was thinking about stream(smth, [])
, but it didn’t work as expected.
Maybe I need to try to pass that at
option.
The old school answer is to have the container of the stream have some id
value that you set from an assign eg id={"items-#{@item_counter}"}
and then you increment the :item_counter
assign when you want to reset the stream.
Not sure if there is a more first class answer these days or not yet. There is a better option now! Phoenix.LiveView — Phoenix LiveView v0.19.5 reset
option on stream/4
1 Like
There’s a reset option on stream/4
3 Likes
You’re right. There’s such an option, I forgot about it.
stream(socket, :songs, [], reset: true)
Thanks. You too, @benwilson512, quite an interesting idea with increment.