I'm making stateless AlertMessageComponent like below, this will be child component.
def render(assigns) do
~L"""
<div class="message is-info">
<div class="alert message-body"><%= live_flash(@flash, "info") %></div>
</div>
"""
end
I'd like to pass flash from parent live view to the child component like this,
<%= live_component @socket, AlertMessageComponent, flash: @flash %>
On the parent live view, I control flash like this,
def handle_event("delete", _params, %{assigns: %{selected: nil}} = socket) do
Process.send_after(self(), "clear_flash", 2000)
socket = put_flash(socket, "info", "There is no delete target.")
{:noreply, socket}
end
def handle_info("clear_flash", socket) do
{:noreply, clear_flash(socket)}
end
In this situation, flash message is presented after cliking phx-click="delete", but it doesn't disappeared.
I changed assigns child component key name from "flash" to "flash2", then works finely.
I'd like to pass flash easily by using key name "flash".
Can you please provide a sample application that reproduces the error? Thank you.
@pojiro I had the same issue and was able to solve it by having a global live view module that mounts all components and putting the flash rendering only at the top level view.
def render_game_or_lobby(assigns) do
if joined?(assigns) do
~L"""
<div class="game-container">
<div class="main-panel">
<%= live_component(@socket, PlayerCardComponent, id: :my_player_card, player: @player) %>
<%= live_component(@socket, DiceRollerComponent, id: :dice_roller, dice_state: @dice_state) %>
</div>
<%= live_component(@socket, PlayerListComponent, players: @players) %>
</div>
"""
else
~L"""
<%= live_component(@socket, LobbyComponent, id: :lobby) %>
"""
end
end
def render(assigns) do
~L"""
<p class="alert alert-info" role="alert"><%= live_flash(@flash, :info) %></p>
<p class="alert alert-danger" role="alert"><%= live_flash(@flash, :error) %></p>
<%= render_game_or_lobby(assigns) %>
"""
end
But I agree with you that this is a weird behavior that flash is not being passed to child components.
@josevalim
I made reproduce application here.
Could you check the behaviour? Reproduce page is root path, http://localhost:4000/.

I'm still seeing this issue on master, the :flash isn't being cleared (actually it's cleared and was cleared even before, the problem is that it doesn't appear in :changed as being changed, and thus the template will still render the old state).
# mix.exs
...
{:phoenix_live_view, github: "phoenixframework/phoenix_live_view"}
I have removed _build and deps folders prior to testing.
I think the problem happens here, where :flash is set to the empty map without :changed being updated to reflect this.
socket = configure_socket_for_component(socket, %{flash: %{}}, %{}, new_fingerprints())
In my attempted PR to fix this, I used Utils.assign(:flash, %{}), which would properly set flash: true in :changed if that was the case.
EDIT: I don't have the needed high level understanding of the entire flow, but just removing the assigning of flash: %{} in this call seems to fix the issue (~all tests are passing as well~):
- socket = configure_socket_for_component(socket, %{flash: %{}}, %{}, new_fingerprints())
+ socket = configure_socket_for_component(socket, %{}, %{}, new_fingerprints())
This is weird. We pass the empty flash on mount, and on mount everything is always considered as changed. I will be glad to accept a PR that removes the flash entry from there altogether but we first need a test that reproduces the issue in our suite. :)
Some further findings:
mount_component gets called after clear_flash if no :id is given to live_component:
defp traverse(
socket,
%Component{id: nil, component: component, assigns: assigns},
fingerprints_tree,
pending_components,
components
) do
rendered = component_to_rendered(socket, component, assigns)
traverse(socket, rendered, fingerprints_tree, pending_components, components)
end
traverse -> component_to_rendered -> mount_component
clear_flash clears the :flash and properly reflects this in :changed, but then, when mount_component is called, it will overwrite the already cleared :flash
So, when :id is given to live_component, everything works as it should and clear_flash is reflected in the template. But, in the case of stateless components (no :id), clear_flash isn't reflected in the template.
Oh, great findings. That should help writing a test for this issue, as the root cause seems to be related to stateless components.
Most helpful comment
@josevalim
I made reproduce application here.
Could you check the behaviour? Reproduce page is root path, http://localhost:4000/.