Skip to content

Commit 09d7470

Browse files
josevalimSteffenDE
authored andcommitted
Keep change tracking if dynamic is empty (#3936)
1 parent 18690a4 commit 09d7470

File tree

2 files changed

+61
-41
lines changed

2 files changed

+61
-41
lines changed

lib/phoenix_live_view/engine.ex

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -776,49 +776,63 @@ defmodule Phoenix.LiveView.Engine do
776776
# Rewrite slots in static parts
777777
static = slots_to_rendered(static, vars, caller, Keyword.get(meta, :slots, []))
778778

779-
cond do
780-
# We can optimize when there are static parts and no dynamic parts.
781-
# We skip live components because they have their own tracking.
782-
static_extra != [] and dynamic == %{} and
783-
not match?({:&, _, [{:/, _, [{:live_component, _, _}, 1]}]}, fun) ->
784-
keys =
785-
for {key, value} <- static_extra,
786-
# We pass empty assigns because if this code is rendered,
787-
# it means that upstream assigns were change tracked.
788-
{_, keys, _} = analyze_and_return_tainted_keys(value, vars, %{}, caller),
789-
# If keys are empty, it is never changed.
790-
keys != %{},
791-
do: {key, to_component_keys(keys)}
792-
793-
# [id: [changed: [:id]], children: [vars_changed: [:children]]]
794-
# -> [changed: [:id], vars_changed: [:children]]
795-
vars_changed_keys =
796-
Enum.flat_map(keys, fn
797-
{_assign, :all} -> []
798-
{_assign, keys} -> keys
799-
end)
800-
801-
static_changed =
802-
quote do
803-
unquote(__MODULE__).to_component_static(
804-
unquote(keys),
805-
unquote(@assigns_var),
806-
changed,
807-
%{unquote_splicing(vars_changed_vars(vars_changed_keys))},
808-
vars_changed
809-
)
810-
end
779+
static =
780+
cond do
781+
# Live components have their own tracking, so we skip the logic below
782+
match?({:&, _, [{:/, _, [{:live_component, _, _}, 1]}]}, fun) ->
783+
static
784+
785+
# Compute change tracking for static parts
786+
static_extra != [] and (dynamic == %{} or without_dependencies?(dynamic, vars, caller)) ->
787+
keys =
788+
for {key, value} <- static_extra,
789+
# We pass empty assigns because if this code is rendered,
790+
# it means that upstream assigns were change tracked.
791+
{_, keys, _} = analyze_and_return_tainted_keys(value, vars, %{}, caller),
792+
# If keys are empty, it is never changed.
793+
keys != %{},
794+
do: {key, to_component_keys(keys)}
795+
796+
# [id: [changed: [:id]], children: [vars_changed: [:children]]]
797+
# -> [changed: [:id], vars_changed: [:children]]
798+
vars_changed_keys =
799+
Enum.flat_map(keys, fn
800+
{_assign, :all} -> []
801+
{_assign, keys} -> keys
802+
end)
803+
804+
static_changed =
805+
quote do
806+
unquote(__MODULE__).to_component_static(
807+
unquote(keys),
808+
unquote(@assigns_var),
809+
changed,
810+
%{unquote_splicing(vars_changed_vars(vars_changed_keys))},
811+
vars_changed
812+
)
813+
end
811814

812-
quote do: %{unquote_splicing([__changed__: static_changed] ++ static)}
815+
[__changed__: static_changed] ++ static
813816

814-
dynamic == %{} ->
815-
quote do: %{unquote_splicing(static)}
817+
true ->
818+
# We must disable change tracking when there is a non empty dynamic part
819+
# (for example `<.my_component {assigns}>`) in case the parent assigns
820+
# already contain a `__changed__` key
821+
[__changed__: nil] ++ static
822+
end
816823

817-
true ->
818-
quote do: Map.merge(unquote(dynamic), %{unquote_splicing(static)})
824+
if dynamic == %{} do
825+
quote do: %{unquote_splicing(static)}
826+
else
827+
quote do: Map.merge(unquote(dynamic), %{unquote_splicing(static)})
819828
end
820829
end
821830

831+
defp without_dependencies?(ast, vars, caller) do
832+
{_, keys, _} = analyze_and_return_tainted_keys(ast, vars, %{}, caller)
833+
keys == %{}
834+
end
835+
822836
defp to_component_keys(:all), do: :all
823837
defp to_component_keys(map), do: Map.keys(map)
824838

test/phoenix_component/rendering_test.exs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ defmodule Phoenix.ComponentRenderingTest do
175175
assigns = %{foo: 1, bar: %{foo: 2}, __changed__: %{}}
176176
assert eval(~H"<.changed foo={@foo} {@bar} />") == [nil]
177177

178-
# we cannot perform any change tracking when dynamic assigns are involved
178+
# We cannot perform any change tracking if dynamic assigns has dependencies
179+
assigns = %{foo: 1, __changed__: %{foo: true}}
180+
assert eval(~H"<.changed foo={@foo} {%{foo: 2}} />") == [["%{foo: true}"]]
181+
179182
assigns = %{foo: 1, bar: %{foo: 2}, __changed__: %{bar: true}}
180183
assert eval(~H"<.changed foo={@foo} {@bar} />") == [["nil"]]
181184

@@ -190,15 +193,18 @@ defmodule Phoenix.ComponentRenderingTest do
190193
assigns = %{foo: %{a: 1, b: 2}, __changed__: %{}}
191194
assert eval(~H"<.changed {@foo} />") == [nil]
192195

193-
# we cannot perform any change tracking when dynamic assigns are involved
196+
# We cannot perform any change tracking if dynamic assigns has dependencies
194197
assigns = %{foo: %{a: 1, b: 2}, __changed__: %{foo: true}}
195198
assert eval(~H"<.changed {@foo} />") == [["nil"]]
196199

197200
assigns = %{foo: %{a: 1, b: 2}, bar: 3, __changed__: %{bar: true}}
198-
assert eval(~H"<.changed {@foo} bar={@bar} />") == [["nil"]]
201+
assert eval(~H"<.changed {%{a: 1, b: 2}} bar={@bar} />") == [["%{bar: true}"]]
199202

200203
assigns = %{foo: %{a: 1, b: 2}, bar: 3, __changed__: %{bar: true}}
201-
assert eval(~H"<.changed {%{a: 1, b: 2}} bar={@bar} />") == [["nil"]]
204+
assert eval(~H"<.changed {assigns} bar={@bar} />") == [["nil"]]
205+
206+
assigns = %{foo: %{a: 1, b: 2}, bar: 3, __changed__: %{bar: true}}
207+
assert eval(~H"<.changed {@foo} bar={@bar} />") == [["nil"]]
202208

203209
assigns = %{bar: 3, __changed__: %{bar: true}}
204210

0 commit comments

Comments
 (0)