diff --git a/src/idom/core/layout.py b/src/idom/core/layout.py index 3d49fceb5..3f4ca5c51 100644 --- a/src/idom/core/layout.py +++ b/src/idom/core/layout.py @@ -234,15 +234,22 @@ def _render_model( raw_model: Any, ) -> None: new_state.model.current = {"tagName": raw_model["tagName"]} - - self._render_model_attributes(old_state, new_state, raw_model) - self._render_model_children(old_state, new_state, raw_model.get("children", [])) - if "key" in raw_model: - new_state.model.current["key"] = raw_model["key"] + new_state.key = new_state.model.current["key"] = raw_model["key"] if "importSource" in raw_model: new_state.model.current["importSource"] = raw_model["importSource"] + if old_state is not None and old_state.key != new_state.key: + self._unmount_model_states([old_state]) + if new_state.is_component_state: + self._model_states_by_life_cycle_state_id[ + new_state.life_cycle_state.id + ] = new_state + old_state = None + + self._render_model_attributes(old_state, new_state, raw_model) + self._render_model_children(old_state, new_state, raw_model.get("children", [])) + def _render_model_attributes( self, old_state: Optional[_ModelState], @@ -604,6 +611,9 @@ def parent(self) -> _ModelState: assert parent is not None, "detached model state" return parent + def __repr__(self) -> str: # pragma: no cover + return f"ModelState({ {s: getattr(self, s, None) for s in self.__slots__} })" + def _make_life_cycle_state( component: ComponentType, diff --git a/temp.py b/temp.py new file mode 100644 index 000000000..5d2a100c4 --- /dev/null +++ b/temp.py @@ -0,0 +1,23 @@ +from random import random + +from idom import component, hooks, html, run + + +@component +def Demo(): + h = hooks.current_hook() + print("render") + return html.div( + html.button({"onClick": lambda event: h.schedule_render()}, "re-render"), + HasState(), + key=str(random()), + ) + + +@component +def HasState(): + state = hooks.use_state(random)[0] + return html.p(state) + + +run(Demo) diff --git a/tests/test_core/test_layout.py b/tests/test_core/test_layout.py index c3bc23f1b..c5f0773a6 100644 --- a/tests/test_core/test_layout.py +++ b/tests/test_core/test_layout.py @@ -1,5 +1,6 @@ import asyncio import gc +import random import re from weakref import finalize from weakref import ref as weakref @@ -811,3 +812,29 @@ def SomeComponent(): set_items.current([]) await layout.render() + + +async def test_changing_key_of_parent_element_unmounts_children(): + random.seed(0) + + root_hook = HookCatcher() + state = idom.Ref(None) + + @idom.component + @root_hook.capture + def Root(): + return idom.html.div(HasState(), key=str(random.random())) + + @idom.component + def HasState(): + state.current = idom.hooks.use_state(random.random)[0] + return idom.html.div() + + with idom.Layout(Root()) as layout: + await layout.render() + + for i in range(5): + last_state = state.current + root_hook.latest.schedule_render() + await layout.render() + assert last_state != state.current