Skip to content

Commit 8c694b7

Browse files
committed
fix issue with key-based identity
keys for elements at the root of a component were not being tracked. thus key changes for elements at the root did not trigger unmounts.
1 parent ad5b333 commit 8c694b7

File tree

3 files changed

+65
-5
lines changed

3 files changed

+65
-5
lines changed

src/idom/core/layout.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,22 @@ def _render_model(
234234
raw_model: Any,
235235
) -> None:
236236
new_state.model.current = {"tagName": raw_model["tagName"]}
237-
238-
self._render_model_attributes(old_state, new_state, raw_model)
239-
self._render_model_children(old_state, new_state, raw_model.get("children", []))
240-
241237
if "key" in raw_model:
242-
new_state.model.current["key"] = raw_model["key"]
238+
new_state.key = new_state.model.current["key"] = raw_model["key"]
243239
if "importSource" in raw_model:
244240
new_state.model.current["importSource"] = raw_model["importSource"]
245241

242+
if old_state is not None and old_state.key != new_state.key:
243+
self._unmount_model_states([old_state])
244+
if new_state.is_component_state:
245+
self._model_states_by_life_cycle_state_id[
246+
new_state.life_cycle_state.id
247+
] = new_state
248+
old_state = None
249+
250+
self._render_model_attributes(old_state, new_state, raw_model)
251+
self._render_model_children(old_state, new_state, raw_model.get("children", []))
252+
246253
def _render_model_attributes(
247254
self,
248255
old_state: Optional[_ModelState],
@@ -604,6 +611,9 @@ def parent(self) -> _ModelState:
604611
assert parent is not None, "detached model state"
605612
return parent
606613

614+
def __repr__(self) -> str:
615+
return f"ModelState({ {s: getattr(self, s, None) for s in self.__slots__} })"
616+
607617

608618
def _make_life_cycle_state(
609619
component: ComponentType,

temp.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from random import random
2+
3+
from idom import component, hooks, html, run
4+
5+
6+
@component
7+
def Demo():
8+
h = hooks.current_hook()
9+
print("render")
10+
return html.div(
11+
html.button({"onClick": lambda event: h.schedule_render()}, "re-render"),
12+
HasState(),
13+
key=str(random()),
14+
)
15+
16+
17+
@component
18+
def HasState():
19+
state = hooks.use_state(random)[0]
20+
return html.p(state)
21+
22+
23+
run(Demo)

tests/test_core/test_layout.py

+27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import gc
3+
import random
34
import re
45
from weakref import finalize
56
from weakref import ref as weakref
@@ -811,3 +812,29 @@ def SomeComponent():
811812
set_items.current([])
812813

813814
await layout.render()
815+
816+
817+
async def test_changing_key_of_parent_element_unmounts_children():
818+
random.seed(0)
819+
820+
root_hook = HookCatcher()
821+
state = idom.Ref(None)
822+
823+
@idom.component
824+
@root_hook.capture
825+
def Root():
826+
return idom.html.div(HasState(), key=str(random.random()))
827+
828+
@idom.component
829+
def HasState():
830+
state.current = idom.hooks.use_state(random.random)[0]
831+
return idom.html.div()
832+
833+
with idom.Layout(Root()) as layout:
834+
await layout.render()
835+
836+
for i in range(5):
837+
last_state = state.current
838+
root_hook.latest.schedule_render()
839+
await layout.render()
840+
assert last_state != state.current

0 commit comments

Comments
 (0)