rx.memo fixes to retain more API surface from the previous implementation#6598
Conversation
…warning Older @rx.memo code (the old custom_component) annotated parameters with bare Python types (e.g. `name: str`) and omitted return annotations. The new rx.memo required rx.Var[...]-typed params and hard-errored otherwise, breaking that code. Coerce bare-type params into rx.Var[...] (children -> rx.Var[rx.Component]) in the public decorator path and flag them so the existing single deprecation warning points the user at the parameters and return type that still need explicit annotations. Strict-mode internal callers keep raising. Adds a deprecation news fragment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The decorator no longer runs the decorated function body at import time. memo() now does only signature-level work (return-annotation check, param analysis, deprecation warning, registration) and stores a placeholder body with _evaluated=False. The body is compiled lazily on first access via _ensure_definition_evaluated, triggered by the component wrapper at instantiation or by the compiler (get_component/get_function). This speeds up import and lets a memo reference modules that aren't fully imported yet, sidestepping circular-import ordering issues during decoration. Body-dependent validation (hooks, non-bundled imports, "must return a component") now surfaces at first use/compile instead of import. Drop _bind_self_reference: recursion now resolves naturally because the decorated name is bound by first use, and the _evaluating re-entrancy guard covers in-eval recursion. The missing-return deprecation hint is now a constant `-> rx.Component`, removing the body-eval-dependent return-type inference (and its namespace-resolution helpers) so the warning stays eager. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an empty-component `rx.Var[rx.Component]` sentinel, the component counterpart to EMPTY_VAR_STR / EMPTY_VAR_INT, for use as a default on `@rx.memo` `children` slots and other `rx.Var[rx.Component]` props. It is defined in `reflex_base.components.memo` rather than `component.py`: materializing a component var eagerly imports `Bare` (and thus `reflex_base.environment`), so defining it in the always-early `component.py` cycles when `environment` is the import entry point. `memo.py` is imported lazily, after `environment` is ready. Exposed as `rx.EMPTY_VAR_COMPONENT`, documented in the memo docs, and used by the internal skeleton component. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR extends
Confidence Score: 4/5Safe to merge; the lazy-evaluation path is well-guarded and the test suite covers recursive memos, bare-type coercion, EMPTY_VAR_COMPONENT, and the deferred-error semantics for hook/import validation. The only concerns are a non-atomic check-and-set of the
Important Files Changed
Reviews (1): Last reviewed commit: "feat(memo): add rx.EMPTY_VAR_COMPONENT d..." | Re-trigger Greptile |
The legacy custom-component @rx.memo accepted base Component props directly on the wrapper — notably `key`, needed when memos are used under rx.foreach. A RestProp-less memo now forwards key/id/class_name/ style/custom_attrs/ref to the rendered component (with a deprecation warning pointing at rx.RestProp) instead of raising. Identity/internal fields (tag, library, event_triggers, ...) stay rejected since overriding them would corrupt the render.
Replace the per-definition _evaluated/_evaluating flags and the shared _ensure_definition_evaluated routine with a dedicated _LazyBody generic that owns deferral, caching, and the re-entrancy guard. MemoDefinition now exposes `.component` / `.function` as plain properties instead of get_component() / get_function() methods, so callers read a value rather than invoke an evaluation step. A placeholder-less re-entrant read now raises instead of silently returning a stale body, surfacing broken invariants loudly.
Python <=3.10's get_type_hints rewrites a `param: X = None` annotation into Optional[X], hiding the real type from the param classifiers — so an EventHandler param with a None default slipped past validation on 3.10 instead of being rejected. Strip the Optional wrapper before classifying, gated behind a version check so 3.11+ skips the extra get_origin work it doesn't need.
The base-prop passthrough deprecation lives in reflex-base, so its news fragment belongs alongside that package rather than in the repo-root news directory.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The component properties I allowed into memos maybe no op, have to test those. Key works that we need the most i think. |
…ed props Other base props (id, class_name, style, custom_attrs, ref) never reach the rendered root without a `...rest` spread, so accepting them just dropped them silently. Reject them with a message pointing at `rx.RestProp`; `key` stays forwardable since React reads it at the reconciliation layer. Gate the deprecation warning per-wrapper so a keyed memo under `rx.foreach` doesn't re-walk the call stack per row.
allow RestProp values to be updated with other dict/objectvar values and still conveniently function as a RestProp.
-> rx.Componentwill sufficechildrenargs can have a default and don't need to be passed.What is still outstanding?
key,id, and/or other base Component props were accepted on the legacyrx.memoCustomComponentclass. Now these require the RestProp. If there was a way to at least keepkeyworking without a lot of hacks, that would be nice, since we have quite a bit of code in the wild that is using memo components with rx.foreach and need to be able to specify the reactkey. Obviously these can and should be rewritten to use RestProp, but I wonder if there's a way we can keep them working and throw up a deprecation warning?