From 423e62c2b8412fc3b86d97213051fc1aac7e3bed Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Thu, 28 May 2026 23:48:34 +0500 Subject: [PATCH 1/3] perf: lazy default resolution for component fields Promote ``ComponentField`` to a non-data descriptor so unset attributes fall back to their default on read instead of being eagerly materialized on every instance during ``__init__``. Cache ``get_event_triggers`` per class and memoize ``to_camel_case`` to cut repeated work on the hot component-creation path. --- .../src/reflex_base/components/component.py | 50 +++++++++++++++++-- .../src/reflex_base/components/field.py | 3 ++ .../src/reflex_base/utils/format.py | 11 ++-- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/components/component.py b/packages/reflex-base/src/reflex_base/components/component.py index 9f68ee46908..21fdba842d8 100644 --- a/packages/reflex-base/src/reflex_base/components/component.py +++ b/packages/reflex-base/src/reflex_base/components/component.py @@ -95,6 +95,35 @@ def __repr__(self) -> str: return f"ComponentField(default={self.default!r}, is_javascript={self.is_javascript!r}{annotated_type_str})" return f"ComponentField(default_factory={self.default_factory!r}, is_javascript={self.is_javascript!r}{annotated_type_str})" + def __get__(self, instance: Any, owner: type[Any] | None = None) -> Any: + """Supply an unset field's default via the descriptor protocol. + + With no ``__set__`` this is a non-data descriptor: an explicitly-set + value in the instance ``__dict__`` shadows it, so only unset fields + reach here. Construction can therefore skip materializing every + default onto every instance and let reads resolve them lazily. + + Args: + instance: The component instance, or ``None`` for class access. + owner: The owning class. + + Returns: + ``self`` for class access, otherwise the default. Factory defaults + are cached on the instance so later in-place mutation persists. + + Raises: + AttributeError: The field has neither a default nor a factory. + """ + if instance is None: + return self + if self.default is not MISSING: + return self.default + if self.default_factory is not None: + value = self.default_factory() + instance.__dict__[self._name] = value + return value + raise AttributeError(self._name) + def field( default: FIELD_TYPE | _MISSING_TYPE = MISSING, @@ -278,6 +307,15 @@ def _finalize_fields( if value.is_javascript is True } + # Install each own field as a class-level descriptor so unset instance + # attributes resolve to their default through ``ComponentField.__get__`` + # (inherited fields resolve via the MRO). Skip names already bound to a + # non-field value so a ``@property`` or literal override keeps winning. + for field_name, field_ in own_fields.items(): + existing = namespace.get(field_name) + if existing is None or isinstance(existing, ComponentField): + namespace[field_name] = field_ + class BaseComponent(metaclass=BaseComponentMeta): """The base class for all Reflex components. @@ -314,9 +352,6 @@ def __init__( """ for key, value in kwargs.items(): setattr(self, key, value) - for name, value in self.get_fields().items(): - if name not in kwargs: - setattr(self, name, value.default_value()) def set(self, **kwargs): """Set the component props. @@ -1070,7 +1105,14 @@ def get_event_triggers(cls) -> dict[str, types.ArgsSpec | Sequence[types.ArgsSpe """ # Look for component specific triggers, # e.g. variable declared as EventHandler types. - return DEFAULT_TRIGGERS | args_specs_from_fields(cls.get_fields()) # pyright: ignore [reportOperatorIssue] + # Cache on the class's own __dict__ (not inherited) so each subclass + # computes its own; the field set is fixed at class creation. + cached = cls.__dict__.get("_event_triggers_cache") + if cached is not None: + return cached + result = DEFAULT_TRIGGERS | args_specs_from_fields(cls.get_fields()) # pyright: ignore [reportOperatorIssue] + cls._event_triggers_cache = result + return result def __repr__(self) -> str: """Represent the component in React. diff --git a/packages/reflex-base/src/reflex_base/components/field.py b/packages/reflex-base/src/reflex_base/components/field.py index 5487bdd923d..413b29d062c 100644 --- a/packages/reflex-base/src/reflex_base/components/field.py +++ b/packages/reflex-base/src/reflex_base/components/field.py @@ -15,6 +15,9 @@ class BaseField(Generic[FIELD_TYPE]): """Base field class used by internal metadata classes.""" + # Set by ``FieldBasedMeta._finalize_fields`` once the owning class is built. + _name: str + def __init__( self, default: FIELD_TYPE | _MISSING_TYPE = MISSING, diff --git a/packages/reflex-base/src/reflex_base/utils/format.py b/packages/reflex-base/src/reflex_base/utils/format.py index c8f11e01f02..35a2bc3e30a 100644 --- a/packages/reflex-base/src/reflex_base/utils/format.py +++ b/packages/reflex-base/src/reflex_base/utils/format.py @@ -6,6 +6,7 @@ import json import os import re +from functools import lru_cache from typing import TYPE_CHECKING, Any from reflex_base import constants @@ -13,13 +14,8 @@ if TYPE_CHECKING: from reflex_base.components.component import ComponentStyle - from reflex_base.event import ( - ArgsSpec, - EventChain, - EventHandler, - EventSpec, - EventType, - ) + from reflex_base.event import EventChain, EventHandler, EventSpec, EventType + from reflex_base.utils.types import ArgsSpec WRAP_MAP = { "{": "}", @@ -174,6 +170,7 @@ def to_snake_case(text: str) -> str: return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).lower().replace("-", "_") +@lru_cache(maxsize=4096) def to_camel_case(text: str, treat_hyphens_as_underscores: bool = True) -> str: """Convert a string to camel case. From 7d36c6b6dd02a06f96db45855fb41cef9b04d10f Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Fri, 29 May 2026 20:39:23 +0500 Subject: [PATCH 2/3] add news fragment for component creation perf (#6576) --- packages/reflex-base/news/6576.performance.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/reflex-base/news/6576.performance.md diff --git a/packages/reflex-base/news/6576.performance.md b/packages/reflex-base/news/6576.performance.md new file mode 100644 index 00000000000..e55e226cbc0 --- /dev/null +++ b/packages/reflex-base/news/6576.performance.md @@ -0,0 +1 @@ +Speed up component creation by resolving field defaults lazily (via class-level descriptors) instead of eagerly on every instance, caching each component class's event triggers, and memoizing `to_camel_case`. From 2319c511bbc248671d91c46877ab2ef21e38f388 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Fri, 29 May 2026 20:45:35 +0500 Subject: [PATCH 3/3] use explicit key-presence check when installing field descriptors (#6576) --- .../src/reflex_base/components/component.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/reflex-base/src/reflex_base/components/component.py b/packages/reflex-base/src/reflex_base/components/component.py index 21fdba842d8..767df549dad 100644 --- a/packages/reflex-base/src/reflex_base/components/component.py +++ b/packages/reflex-base/src/reflex_base/components/component.py @@ -309,11 +309,13 @@ def _finalize_fields( # Install each own field as a class-level descriptor so unset instance # attributes resolve to their default through ``ComponentField.__get__`` - # (inherited fields resolve via the MRO). Skip names already bound to a - # non-field value so a ``@property`` or literal override keeps winning. + # (inherited fields resolve via the MRO). A name bound to a plain value + # — a ``@property``, method, or literal default — serves the attribute + # itself, so only absent names and field() markers get the descriptor. for field_name, field_ in own_fields.items(): - existing = namespace.get(field_name) - if existing is None or isinstance(existing, ComponentField): + if field_name not in namespace or isinstance( + namespace[field_name], ComponentField + ): namespace[field_name] = field_