@@ -129,6 +129,11 @@ def field(
129129 if default is not MISSING and default_factory is not None :
130130 msg = "cannot specify both default and default_factory"
131131 raise ValueError (msg )
132+ if default is not MISSING and not types .is_immutable (default ):
133+ console .warn (
134+ "Mutable default values are not recommended. "
135+ "Use default_factory instead to avoid unexpected behavior."
136+ )
132137 return ComponentField ( # pyright: ignore [reportReturnType]
133138 default = default ,
134139 default_factory = default_factory ,
@@ -146,6 +151,9 @@ class BaseComponentMeta(FieldBasedMeta, ABCMeta):
146151 _own_fields : Mapping [str , ComponentField ]
147152 _fields : Mapping [str , ComponentField ]
148153 _js_fields : Mapping [str , ComponentField ]
154+ _immutable_field_defaults : Mapping [str , Any ]
155+ _factory_field_defaults : Mapping [str , Callable [[], Any ]]
156+ _required_fields : tuple [str , ...]
149157
150158 @classmethod
151159 def _process_annotated_fields (
@@ -306,11 +314,23 @@ def __init__(
306314 """
307315 if "children" in kwargs :
308316 kwargs ["children" ] = tuple (kwargs ["children" ])
309- for key , value in kwargs .items ():
310- setattr (self , key , value )
311- for name , value in self .get_fields ().items ():
317+ # Bypass ``__setattr__``'s freeze guard: a brand-new instance can't be
318+ # frozen, so the per-attribute check is pure overhead during init.
319+ d = vars (self )
320+ d .update (kwargs )
321+ cls = type (self )
322+ # Defaults are pre-resolved by the metaclass into immutable/factory
323+ # buckets, so we avoid the ``BaseField.default_value`` dispatch.
324+ for name , default in cls ._immutable_field_defaults .items ():
312325 if name not in kwargs :
313- setattr (self , name , value .default_value ())
326+ d [name ] = default
327+ for name , factory in cls ._factory_field_defaults .items ():
328+ if name not in kwargs :
329+ d [name ] = factory ()
330+ for name in cls ._required_fields :
331+ if name not in kwargs :
332+ msg = f"Field `{ name } ` of `{ cls .__name__ } ` requires a value."
333+ raise ValueError (msg )
314334
315335 def __setattr__ (self , key : str , value : Any ) -> None :
316336 """Block writes to frozen components, except for cache attributes.
@@ -946,76 +966,70 @@ def _post_init(self, *args, **kwargs):
946966 component_specific_triggers = self .get_event_triggers ()
947967 props = self .get_props ()
948968
949- # Add any events triggers.
950- if "event_triggers" not in kwargs :
951- kwargs ["event_triggers" ] = {}
952- kwargs ["event_triggers" ] = kwargs ["event_triggers" ].copy ()
969+ # Lazily allocate the event_triggers dict only when a trigger is found.
970+ # Most components have no events; allocating up-front is pure waste.
971+ existing_triggers = kwargs .get ("event_triggers" )
972+ event_triggers : dict [str , Any ] | None = (
973+ dict (existing_triggers ) if existing_triggers else None
974+ )
975+ event_keys : list [str ] = []
953976
954977 # Iterate through the kwargs and set the props.
955978 for key , value in kwargs .items ():
956- if (
957- key .startswith ("on_" )
958- and key not in component_specific_triggers
959- and key not in props
960- ):
961- valid_triggers = sorted (component_specific_triggers .keys ())
962- msg = (
963- f"The { (comp_name := type (self ).__name__ )} does not take in an `{ key } ` event trigger. "
964- f"Valid triggers for { comp_name } : { valid_triggers } . "
965- f"If { comp_name } is a third party component make sure to add `{ key } ` to the component's event triggers. "
966- f"visit https://reflex.dev/docs/wrapping-react/guide/#event-triggers for more info."
967- )
968- raise ValueError (msg )
969979 if key in component_specific_triggers :
970- # Event triggers are bound to event chains.
971- is_var = False
972- elif key in props :
973- # Set the field type.
974- is_var = (
975- field . type_origin is Var if ( field := fields . get ( key )) else False
980+ if event_triggers is None :
981+ event_triggers = {}
982+ event_triggers [ key ] = EventChain . create (
983+ value = value ,
984+ args_spec = component_specific_triggers [ key ],
985+ key = key ,
976986 )
977- else :
987+ event_keys . append ( key )
978988 continue
979989
980- # Check whether the key is a component prop.
981- if is_var :
990+ if key in props :
991+ field = fields .get (key )
992+ if field is None or field .type_origin is not Var :
993+ continue
982994 try :
983995 kwargs [key ] = LiteralVar .create (value )
984-
985- # Get the passed type and the var type.
986996 passed_type = kwargs [key ]._var_type
987997 expected_type = typing .get_args (
988998 types .get_field_type (type (self ), key )
989999 )[0 ]
9901000 except TypeError :
991- # If it is not a valid var, check the base types.
9921001 passed_type = type (value )
9931002 expected_type = types .get_field_type (type (self ), key )
9941003
9951004 if not satisfies_type_hint (value , expected_type ):
9961005 value_name = value ._js_expr if isinstance (value , Var ) else value
997-
9981006 additional_info = (
9991007 " You can call `.bool()` on the value to convert it to a boolean."
10001008 if expected_type is bool and isinstance (value , Var )
10011009 else ""
10021010 )
1003-
10041011 raise TypeError (
10051012 f"Invalid var passed for prop { type (self ).__name__ } .{ key } , expected type { expected_type } , got value { value_name } of type { passed_type } ."
10061013 + additional_info
10071014 )
1008- # Check if the key is an event trigger.
1009- if key in component_specific_triggers :
1010- kwargs ["event_triggers" ][key ] = EventChain .create (
1011- value = value ,
1012- args_spec = component_specific_triggers [key ],
1013- key = key ,
1015+ continue
1016+
1017+ if key .startswith ("on_" ):
1018+ valid_triggers = sorted (component_specific_triggers .keys ())
1019+ comp_name = type (self ).__name__
1020+ msg = (
1021+ f"The { comp_name } does not take in an `{ key } ` event trigger. "
1022+ f"Valid triggers for { comp_name } : { valid_triggers } . "
1023+ f"If { comp_name } is a third party component make sure to add `{ key } ` to the component's event triggers. "
1024+ f"visit https://reflex.dev/docs/wrapping-react/guide/#event-triggers for more info."
10141025 )
1026+ raise ValueError (msg )
10151027
1016- # Remove any keys that were added as events.
1017- for key in kwargs ["event_triggers" ]:
1018- kwargs .pop (key , None )
1028+ # Promote any registered event triggers; drop the raw on_* keys.
1029+ if event_triggers is not None :
1030+ kwargs ["event_triggers" ] = event_triggers
1031+ for key in event_keys :
1032+ kwargs .pop (key , None )
10191033
10201034 # Place data_ and aria_ attributes into custom_attrs
10211035 special_attributes = [
@@ -1086,9 +1100,9 @@ def _post_init(self, *args, **kwargs):
10861100 ):
10871101 msg = f"Invalid class_name passed for prop { type (self ).__name__ } .class_name, expected type str, got value { class_name ._js_expr } of type { class_name ._var_type } ."
10881102 raise TypeError (msg )
1089- # Construct the component.
1090- for key , value in kwargs . items ():
1091- setattr (self , key , value )
1103+ # Construct the component. Bypass ``__setattr__``'s freeze guard: the
1104+ # instance is freshly created and not yet frozen.
1105+ vars (self ). update ( kwargs )
10921106
10931107 @classmethod
10941108 def get_event_triggers (cls ) -> dict [str , types .ArgsSpec | Sequence [types .ArgsSpec ]]:
@@ -1300,8 +1314,8 @@ def _unsafe_create(
13001314 children_tuple = tuple (children )
13011315 comp = cls .__new__ (cls )
13021316 super (Component , comp ).__init__ (id = props .get ("id" ), children = children_tuple )
1303- for prop , value in props . items ():
1304- setattr (comp , prop , value )
1317+ # Bypass ``__setattr__``'s freeze guard: ``comp`` is not yet frozen.
1318+ vars (comp ). update ( props )
13051319 comp ._freeze ()
13061320 return comp
13071321
@@ -1533,19 +1547,18 @@ def _validate_component_children(self, children: list[Component]):
15331547 children: The children of the component.
15341548
15351549 """
1536- from reflex_components_core .base .fragment import Fragment
1537- from reflex_components_core .core .cond import Cond
1538- from reflex_components_core .core .foreach import Foreach
1539- from reflex_components_core .core .match import Match
1540-
1541- no_valid_parents_defined = all (child ._valid_parents == [] for child in children )
15421550 if (
15431551 not self ._invalid_children
15441552 and not self ._valid_children
1545- and no_valid_parents_defined
1553+ and all ( child . _valid_parents == [] for child in children )
15461554 ):
15471555 return
15481556
1557+ from reflex_components_core .base .fragment import Fragment
1558+ from reflex_components_core .core .cond import Cond
1559+ from reflex_components_core .core .foreach import Foreach
1560+ from reflex_components_core .core .match import Match
1561+
15491562 comp_name = type (self ).__name__
15501563 allowed_components = [
15511564 comp .__name__ for comp in (Fragment , Foreach , Cond , Match )
0 commit comments