@@ -100,7 +100,7 @@ def __enter__(self: _Self) -> _Self:
100100 def __exit__ (self , * exc : Any ) -> None :
101101 root_csid = self ._root_life_cycle_state_id
102102 root_model_state = self ._model_states_by_life_cycle_state_id [root_csid ]
103- self ._unmount_model_states ([root_model_state ])
103+ self ._deep_unmount_model_states ([root_model_state ])
104104
105105 # delete attributes here to avoid access after exiting context manager
106106 del self ._event_handlers
@@ -166,7 +166,7 @@ def _create_layout_update(self, old_state: _ModelState) -> LayoutUpdate:
166166
167167 # hook effects must run after the update is complete
168168 for model_state in _iter_model_state_children (new_state ):
169- if hasattr ( model_state , "life_cycle_state" ) :
169+ if model_state . is_component_state :
170170 model_state .life_cycle_state .hook .component_did_render ()
171171
172172 old_model : Optional [VdomJson ]
@@ -271,10 +271,10 @@ def _render_model_attributes(
271271
272272 model_event_handlers = new_state .model .current ["eventHandlers" ] = {}
273273 for event , handler in handlers_by_event .items ():
274- target = old_state .targets_by_event . get (
275- event ,
276- uuid4 (). hex if handler . target is None else handler . target ,
277- )
274+ if event in old_state .targets_by_event :
275+ target = old_state . targets_by_event [ event ]
276+ else :
277+ target = uuid4 (). hex if handler . target is None else handler . target
278278 new_state .targets_by_event [event ] = target
279279 self ._event_handlers [target ] = handler
280280 model_event_handlers [event ] = {
@@ -320,7 +320,7 @@ def _render_model_children(
320320 self ._render_model_children_without_old_state (new_state , raw_children )
321321 return None
322322 elif not raw_children :
323- self ._unmount_model_states (list (old_state .children_by_key .values ()))
323+ self ._deep_unmount_model_states (list (old_state .children_by_key .values ()))
324324 return None
325325
326326 child_type_key_tuples = list (_process_child_type_and_key (raw_children ))
@@ -335,12 +335,13 @@ def _render_model_children(
335335
336336 old_keys = set (old_state .children_by_key ).difference (new_keys )
337337 if old_keys :
338- self ._unmount_model_states (
338+ self ._deep_unmount_model_states (
339339 [old_state .children_by_key [key ] for key in old_keys ]
340340 )
341341
342342 new_children = new_state .model .current ["children" ] = []
343343 for index , (child , child_type , key ) in enumerate (child_type_key_tuples ):
344+ old_child_state = old_state .children_by_key .get (key )
344345 if child_type is _DICT_TYPE :
345346 old_child_state = old_state .children_by_key .get (key )
346347 if old_child_state is None :
@@ -350,6 +351,8 @@ def _render_model_children(
350351 key ,
351352 )
352353 else :
354+ if old_child_state .is_component_state :
355+ self ._shallow_unmount_model_state (old_child_state )
353356 new_child_state = _update_element_model_state (
354357 old_child_state ,
355358 new_state ,
@@ -374,9 +377,13 @@ def _render_model_children(
374377 new_state ,
375378 index ,
376379 child ,
380+ self ._rendering_queue .put ,
377381 )
378382 self ._render_component (old_child_state , new_child_state , child )
379383 else :
384+ old_child_state = old_state .children_by_key .get (key )
385+ if old_child_state is not None :
386+ self ._deep_unmount_model_states ([old_child_state ])
380387 new_children .append (child )
381388
382389 def _render_model_children_without_old_state (
@@ -399,20 +406,21 @@ def _render_model_children_without_old_state(
399406 else :
400407 new_children .append (child )
401408
402- def _unmount_model_states (self , old_states : List [_ModelState ]) -> None :
409+ def _deep_unmount_model_states (self , old_states : List [_ModelState ]) -> None :
403410 to_unmount = old_states [::- 1 ] # unmount in reversed order of rendering
404411 while to_unmount :
405412 model_state = to_unmount .pop ()
413+ self ._shallow_unmount_model_state (model_state )
414+ to_unmount .extend (model_state .children_by_key .values ())
406415
407- for target in model_state .targets_by_event .values ():
408- del self ._event_handlers [target ]
409-
410- if hasattr (model_state , "life_cycle_state" ):
411- life_cycle_state = model_state .life_cycle_state
412- del self ._model_states_by_life_cycle_state_id [life_cycle_state .id ]
413- life_cycle_state .hook .component_will_unmount ()
416+ def _shallow_unmount_model_state (self , old_state : _ModelState ) -> None :
417+ for target in old_state .targets_by_event .values ():
418+ del self ._event_handlers [target ]
414419
415- to_unmount .extend (model_state .children_by_key .values ())
420+ if old_state .is_component_state :
421+ life_cycle_state = old_state .life_cycle_state
422+ del self ._model_states_by_life_cycle_state_id [life_cycle_state .id ]
423+ life_cycle_state .hook .component_will_unmount ()
416424
417425 def __repr__ (self ) -> str :
418426 return f"{ type (self ).__name__ } ({ self .root } )"
@@ -459,7 +467,6 @@ def _make_component_model_state(
459467
460468
461469def _copy_component_model_state (old_model_state : _ModelState ) -> _ModelState :
462-
463470 # use try/except here because not having a parent is rare (only the root state)
464471 try :
465472 parent : Optional [_ModelState ] = old_model_state .parent
@@ -483,15 +490,8 @@ def _update_component_model_state(
483490 new_parent : _ModelState ,
484491 new_index : int ,
485492 new_component : ComponentType ,
493+ schedule_render : Callable [[_LifeCycleStateId ], None ],
486494) -> _ModelState :
487- try :
488- old_life_cycle_state = old_model_state .life_cycle_state
489- except AttributeError :
490- raise ValueError (
491- f"Failed to render layout at { old_model_state .patch_path !r} with key "
492- f"{ old_model_state .key !r} - prior element with this key wasn't a component"
493- )
494-
495495 return _ModelState (
496496 parent = new_parent ,
497497 index = new_index ,
@@ -500,7 +500,11 @@ def _update_component_model_state(
500500 patch_path = old_model_state .patch_path ,
501501 children_by_key = {},
502502 targets_by_event = {},
503- life_cycle_state = _update_life_cycle_state (old_life_cycle_state , new_component ),
503+ life_cycle_state = (
504+ _update_life_cycle_state (old_model_state .life_cycle_state , new_component )
505+ if old_model_state .is_component_state
506+ else _make_life_cycle_state (new_component , schedule_render )
507+ ),
504508 )
505509
506510
@@ -525,12 +529,6 @@ def _update_element_model_state(
525529 new_parent : _ModelState ,
526530 new_index : int ,
527531) -> _ModelState :
528- if hasattr (old_model_state , "life_cycle_state" ):
529- raise ValueError (
530- f"Failed to render layout at { old_model_state .patch_path !r} with key "
531- f"{ old_model_state .key !r} - prior element with this key was a component"
532- )
533-
534532 return _ModelState (
535533 parent = new_parent ,
536534 index = new_index ,
@@ -597,6 +595,10 @@ def __init__(
597595 self .life_cycle_state = life_cycle_state
598596 """The state for the element's component (if it has one)"""
599597
598+ @property
599+ def is_component_state (self ) -> bool :
600+ return hasattr (self , "life_cycle_state" )
601+
600602 @property
601603 def parent (self ) -> _ModelState :
602604 parent = self ._parent_ref ()
0 commit comments