Skip to content

Commit f491d40

Browse files
authored
Ensure change tracking is enabled when traversing keyed comprehensions (#3904)
When rendering live component that shared statics, we'd traverse with change tracking disabled. The following situation could happen: LiveComponent 1 renders a keyed comprehension with one entry. LiveComponent 2 renders a keyed comprehension with more entries, but the first fingerprint still matches. Now, when rendering the second entry (which is new), we'd build a new template map, which could be incompatible with the first component. Then, when rendering, the first entry of the keyed comprehension could point to invalid statics. The solution is to always consider elements as new when change tracking is disabled.
1 parent 92ce429 commit f491d40

File tree

2 files changed

+66
-2
lines changed

2 files changed

+66
-2
lines changed

lib/phoenix_live_view/diff.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -675,8 +675,8 @@ defmodule Phoenix.LiveView.Diff do
675675
end
676676
end
677677

678-
# it's an existing entry
679-
defp process_keyed({key, new_vars, render}, previous_prints, changed?, stream?, acc)
678+
# it's an existing entry and we are change tracking
679+
defp process_keyed({key, new_vars, render}, previous_prints, true = changed?, stream?, acc)
680680
when is_map_key(previous_prints, key) and not stream? do
681681
{diff, index, new_prints, pending, components, template} = acc
682682

test/phoenix_live_view/diff_test.exs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2479,5 +2479,69 @@ defmodule Phoenix.LiveView.DiffTest do
24792479
}
24802480
}
24812481
end
2482+
2483+
defmodule LiveComponentForIssue3904 do
2484+
# https://github.com/phoenixframework/phoenix_live_view/pull/3904
2485+
use Phoenix.LiveComponent
2486+
2487+
def render(assigns) do
2488+
~H"""
2489+
<div>
2490+
<div :for={i <- 1..assigns.count}>
2491+
Shared
2492+
<%= if i == 2 do %>
2493+
1 {2} 3
2494+
<% end %>
2495+
<%= if i > 0 do %>
2496+
Always
2497+
<% end %>
2498+
</div>
2499+
</div>
2500+
"""
2501+
end
2502+
end
2503+
2504+
test "considers element rendered in shared live component trees as new" do
2505+
assigns = %{}
2506+
2507+
template = ~H"""
2508+
<.live_component id={0} module={LiveComponentForIssue3904} count={1} />
2509+
<.live_component id={1} module={LiveComponentForIssue3904} count={2} />
2510+
"""
2511+
2512+
{full_render, _fingerprints, _components} = render(template)
2513+
2514+
assert full_render == %{
2515+
0 => 1,
2516+
1 => 2,
2517+
:c => %{
2518+
1 => %{
2519+
0 => %{
2520+
p: %{0 => ["\n Always\n "]},
2521+
s: ["<div>\n Shared\n ", "\n ", "\n </div>"],
2522+
k: %{0 => %{0 => "", 1 => %{s: 0}}, :kc => 1}
2523+
},
2524+
:r => 1,
2525+
:s => ["<div>\n ", "\n</div>"]
2526+
},
2527+
2 => %{
2528+
0 => %{
2529+
p: %{0 => ["\n Always\n "], 1 => ["\n 1 ", " 3\n "]},
2530+
k: %{
2531+
0 => %{0 => "", 1 => %{s: 0}},
2532+
1 => %{0 => %{0 => "2", :s => 1}, 1 => %{s: 0}},
2533+
:kc => 2
2534+
}
2535+
},
2536+
:s => 1
2537+
}
2538+
},
2539+
:p => %{0 => ["", "\n", ""]},
2540+
:s => 0
2541+
}
2542+
2543+
assert rendered_to_binary(full_render) ==
2544+
"<div>\n <div>\n Shared\n \n \n Always\n \n </div>\n</div>\n<div>\n <div>\n Shared\n \n \n Always\n \n </div><div>\n Shared\n \n 1 2 3\n \n \n Always\n \n </div>\n</div>"
2545+
end
24822546
end
24832547
end

0 commit comments

Comments
 (0)