1111 wait ,
1212)
1313from collections import Counter
14- from collections .abc import Iterator
14+ from collections .abc import Sequence
1515from contextlib import AsyncExitStack
1616from logging import getLogger
1717from typing import (
2727from weakref import ref as weakref
2828
2929from anyio import Semaphore
30+ from typing_extensions import TypeAlias
3031
3132from reactpy .config import (
3233 REACTPY_ASYNC_RENDERING ,
3738from reactpy .core .types import (
3839 ComponentType ,
3940 EventHandlerDict ,
41+ Key ,
4042 LayoutEventMessage ,
4143 LayoutUpdateMessage ,
44+ VdomChild ,
4245 VdomDict ,
4346 VdomJson ,
4447)
@@ -189,9 +192,7 @@ async def _render_component(
189192 # wrap the model in a fragment (i.e. tagName="") to ensure components have
190193 # a separate node in the model state tree. This could be removed if this
191194 # components are given a node in the tree some other way
192- wrapper_model : VdomDict = {"tagName" : "" }
193- if raw_model is not None :
194- wrapper_model ["children" ] = [raw_model ]
195+ wrapper_model : VdomDict = {"tagName" : "" , "children" : [raw_model ]}
195196 await self ._render_model (exit_stack , old_state , new_state , wrapper_model )
196197 except Exception as error :
197198 logger .exception (f"Failed to render { component } " )
@@ -329,11 +330,11 @@ async def _render_model_children(
329330 await self ._unmount_model_states (list (old_state .children_by_key .values ()))
330331 return None
331332
332- child_type_key_tuples = list ( _process_child_type_and_key ( raw_children ) )
333+ children_info = _get_children_info ( raw_children )
333334
334- new_keys = {item [ 2 ] for item in child_type_key_tuples }
335- if len (new_keys ) != len (raw_children ):
336- key_counter = Counter (item [2 ] for item in child_type_key_tuples )
335+ new_keys = {k for _ , _ , k in children_info }
336+ if len (new_keys ) != len (children_info ):
337+ key_counter = Counter (item [2 ] for item in children_info )
337338 duplicate_keys = [key for key , count in key_counter .items () if count > 1 ]
338339 msg = f"Duplicate keys { duplicate_keys } at { new_state .patch_path or '/' !r} "
339340 raise ValueError (msg )
@@ -345,7 +346,7 @@ async def _render_model_children(
345346 )
346347
347348 new_state .model .current ["children" ] = []
348- for index , (child , child_type , key ) in enumerate (child_type_key_tuples ):
349+ for index , (child , child_type , key ) in enumerate (children_info ):
349350 old_child_state = old_state .children_by_key .get (key )
350351 if child_type is _DICT_TYPE :
351352 old_child_state = old_state .children_by_key .get (key )
@@ -420,17 +421,17 @@ async def _render_model_children_without_old_state(
420421 new_state : _ModelState ,
421422 raw_children : list [Any ],
422423 ) -> None :
423- child_type_key_tuples = list ( _process_child_type_and_key ( raw_children ) )
424+ children_info = _get_children_info ( raw_children )
424425
425- new_keys = {item [ 2 ] for item in child_type_key_tuples }
426- if len (new_keys ) != len (raw_children ):
427- key_counter = Counter (item [ 2 ] for item in child_type_key_tuples )
426+ new_keys = {k for _ , _ , k in children_info }
427+ if len (new_keys ) != len (children_info ):
428+ key_counter = Counter (k for _ , _ , k in children_info )
428429 duplicate_keys = [key for key , count in key_counter .items () if count > 1 ]
429430 msg = f"Duplicate keys { duplicate_keys } at { new_state .patch_path or '/' !r} "
430431 raise ValueError (msg )
431432
432433 new_state .model .current ["children" ] = []
433- for index , (child , child_type , key ) in enumerate (child_type_key_tuples ):
434+ for index , (child , child_type , key ) in enumerate (children_info ):
434435 if child_type is _DICT_TYPE :
435436 child_state = _make_element_model_state (new_state , index , key )
436437 await self ._render_model (exit_stack , None , child_state , child )
@@ -609,7 +610,7 @@ def __init__(
609610 key : Any ,
610611 model : Ref [VdomJson ],
611612 patch_path : str ,
612- children_by_key : dict [str , _ModelState ],
613+ children_by_key : dict [Key , _ModelState ],
613614 targets_by_event : dict [str , str ],
614615 life_cycle_state : _LifeCycleState | None = None ,
615616 ):
@@ -720,16 +721,17 @@ async def get(self) -> _Type:
720721 return value
721722
722723
723- def _process_child_type_and_key (
724- children : list [Any ],
725- ) -> Iterator [tuple [Any , _ElementType , Any ]]:
724+ def _get_children_info (children : list [VdomChild ]) -> Sequence [_ChildInfo ]:
725+ infos : list [_ChildInfo ] = []
726726 for index , child in enumerate (children ):
727- if isinstance (child , dict ):
727+ if child is None :
728+ continue
729+ elif isinstance (child , dict ):
728730 child_type = _DICT_TYPE
729731 key = child .get ("key" )
730732 elif isinstance (child , ComponentType ):
731733 child_type = _COMPONENT_TYPE
732- key = getattr ( child , " key" , None )
734+ key = child . key
733735 else :
734736 child = f"{ child } "
735737 child_type = _STRING_TYPE
@@ -738,8 +740,12 @@ def _process_child_type_and_key(
738740 if key is None :
739741 key = index
740742
741- yield ( child , child_type , key )
743+ infos . append (( child , child_type , key ) )
742744
745+ return infos
746+
747+
748+ _ChildInfo : TypeAlias = tuple [Any , "_ElementType" , Key ]
743749
744750# used in _process_child_type_and_key
745751_ElementType = NewType ("_ElementType" , int )
0 commit comments