refactor: freeze components after create and use copy_with for mutations#6468
refactor: freeze components after create and use copy_with for mutations#6468FarhanAliRaza wants to merge 4 commits intoreflex-dev:mainfrom
Conversation
Components are now immutable after construction (children stored as tuples, __setattr__ blocks writes outside a small cache allowlist). Compile-time edits go through a new copy_with() helper instead of mutating shared instances, replacing the PageContext.own() page-local clone mechanism so components can be safely reused across pages without deep copies.
# Conflicts: # reflex/experimental/memo.py
Greptile SummaryThis PR makes
Confidence Score: 3/5The freeze contract and copy_with migration are architecturally correct and well-tested, but a TextArea layout regression and a DataTable ref omission need to be addressed before this is safe to merge. TextArea.add_style missed a load-bearing side-effect of the original pop: padding now appears in both the outer container style and the nested & textarea selector for any TextArea with an explicit padding prop, producing incorrect layout. The DataTable ref omission is narrower but is still a silent behavioral change on an existing render path. text_area.py needs its add_style migration to preserve the pop-padding semantics; datatable.py should restore the ref injection logic that was lost when switching to the explicit props= path. Important Files Changed
|
| props = { | ||
| attr.removesuffix("_"): getattr(self, attr) for attr in self.get_props() | ||
| } | ||
| props["columns"] = columns | ||
| props["data"] = data | ||
| return super()._render(props=props) |
There was a problem hiding this comment.
ref silently dropped when DataTable has an id
Component._render(props=None) auto-populates ref via self.ref / self.get_ref() only in the props is None branch. DataTable._render now builds the props dict itself and passes it to super()._render(props=props), which takes the else branch and skips that logic entirely. Any DataTable constructed with an explicit id would previously have had a React ref included in the rendered output; that ref is silently absent now.
Merging this PR will improve performance by 3.68%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | test_get_all_imports_single_pass[_complicated_page] |
1.6 ms | 1.5 ms | +3.68% |
Comparing FarhanAliRaza:immutability (78f1602) with main (2df5344)
Footnotes
-
2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
Bypass __setattr__'s freeze guard during init via vars(self).update(...); a brand-new instance can't be frozen, so the per-attribute check is pure overhead. Applied in BaseComponent.__init__, Component.__init__, and Component._create. In Component.__init__: - Lazily allocate event_triggers only when a trigger is actually seen; most components have none, so the up-front dict copy was wasted work. - Single-pass kwargs loop that handles event triggers, var props, and unknown on_* errors inline instead of double-iterating. - Defer the _validate_children imports until after the fast no-op exit.
5500fc4 to
d1d22c5
Compare
Use so an explicitly-passed empty event_triggers dict still gets copied instead of being dropped. Rename the local to to avoid shadowing the imported helper.
Components are now immutable after construction (children stored as tuples, setattr blocks writes outside a small cache allowlist). Compile-time edits go through a new copy_with() helper instead of mutating shared instances, replacing the PageContext.own() page-local clone mechanism so components can be safely reused across pages without deep copies.
All Submissions:
Type of change
Please delete options that are not relevant.
New Feature Submission:
Changes To Core Features:
After these steps, you're ready to open a pull request.