Skip to content

Experimental memo is !experimental #6517

Draft
FarhanAliRaza wants to merge 8 commits into
reflex-dev:mainfrom
FarhanAliRaza:non-exp-memo
Draft

Experimental memo is !experimental #6517
FarhanAliRaza wants to merge 8 commits into
reflex-dev:mainfrom
FarhanAliRaza:non-exp-memo

Conversation

@FarhanAliRaza
Copy link
Copy Markdown
Contributor

@FarhanAliRaza FarhanAliRaza commented May 15, 2026

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

New Feature Submission:

  • Does your submission pass the tests?
  • Have you linted your code locally prior to submission?

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

fixes ENG-9486

Move the memo/custom-component machinery from
``reflex/experimental/memo.py`` and ``reflex_base.components.component``
into a dedicated ``reflex_base.components.memo`` module and expose it as
``rx.memo``. ``rx.experimental.memo`` becomes a deprecated alias, and the
legacy ``CustomComponent`` index path in the compiler is dropped now that
all memos declare their library per-file.
Component-returning `@rx._x.memo` functions can now declare
`rx.EventHandler[...]` (and bare `rx.EventHandler`) parameters, which compile
to destructured JSX prop callbacks and are wired through `EventChain` at the
call site. Var-returning memos still reject event handlers.

Refactors per-parameter behavior into a `_MemoParamSpec` table keyed by a new
`MemoParamKind` enum (VALUE / CHILDREN / REST / EVENT_TRIGGER), so each kind
owns its classification, validation, placeholder construction, call-site
binding, and JSX signature emission. Adds a `_MemoCallBinding` accumulator
so `_post_init` no longer special-cases prop vs. rest vs. event routing.
Add explicit rx.Component return annotations to memoized helpers across
docs and internal packages, and narrow arrow_svg_component's class_name
to Var[str] now that rx.memo handles the conversion.
@FarhanAliRaza FarhanAliRaza requested review from a team and Alek99 as code owners May 15, 2026 18:38
@FarhanAliRaza FarhanAliRaza marked this pull request as draft May 15, 2026 18:39
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 15, 2026

Greptile Summary

This PR graduates the rx._x.memo experimental decorator to stable rx.memo, replacing the legacy CustomComponent-based implementation with the new ExperimentalMemo* pipeline. The move consolidates all memo logic into packages/reflex-base/src/reflex_base/components/memo.py, deletes CustomComponent/CUSTOM_COMPONENTS, and updates all downstream call sites, tests, and documentation.

  • rx.memo now supports both component-returning and Var-returning functions, requires rx.Var[...]/rx.RestProp/rx.EventHandler annotations on every parameter (breaking the old plain-type API), and emits one per-memo JS module for better tree-shaking.
  • rx._x.memo is kept as a deprecated property that emits a one-time migration warning and delegates to the promoted rx.memo.
  • The integration test suite migrates from Selenium to Playwright, and unit tests gain extensive seam-level coverage of the new MemoParamKind-based classification pipeline.

Confidence Score: 4/5

Safe to merge with two minor items addressed: add MemoParam/MemoParamKind to all, and consider call-site validation for omitted required EventHandler props.

The promotion is well-structured with thorough unit tests and a clear migration path documented. The two open items are quality-of-life issues that don't affect compilation correctness on the happy path, which is fully exercised by the test suite. The legacy CustomComponent removal is complete and consistent across all call sites.

packages/reflex-base/src/reflex_base/components/memo.py — the all list and bind* silent-skip behaviour warrant a second look before stabilising the public API surface.

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/components/memo.py New 1,542-line file: core of the promotion — defines MemoParamKind, MemoParam, _SPECS dispatch table, _MemoCallBinding, ExperimentalMemo* types, and the public memo() decorator. MemoParam and MemoParamKind are used externally (compiler utils, tests) but not in all.
packages/reflex-base/src/reflex_base/components/component.py CustomComponent, CUSTOM_COMPONENTS, custom_component, and the memo alias removed; unneeded imports cleaned up. Straightforward deletion.
reflex/compiler/compiler.py compile_memo_components drops the components positional arg and removes all legacy CUSTOM_COMPONENTS compilation; index is now emitted empty. Imports updated to reflex_base.components.memo.
reflex/compiler/utils.py compile_custom_component and CAMEL_CASE_MEMO_MARKER references removed; compile_experimental_component_memo updated to use param.signature_field() and MemoParamKind enum checks.
reflex/experimental/init.py memo converted from a direct SimpleNamespace constructor kwarg to a @Property that emits a deprecation warning and returns the new rx.memo. memo removed from the _x constructor call.
reflex/components/memo.py New thin shim: wildcard re-export from reflex_base.components.memo with a pyright suppress comment.
tests/units/experimental/test_memo.py Extensive new tests covering EventHandler params, _MemoParamSpec seam, _MemoCallBinding routing, signature_field per kind, and classify ordering. Imports updated to reflex_base.components.memo.
tests/integration/tests_playwright/test_memo.py New Playwright integration test replacing the deleted Selenium test_memo.py; covers EventHandler partial application and raw pass-through via module-scoped fixture.
reflex/testing.py AppHarness._initialize_app now only clears EXPERIMENTAL_MEMOS (removing CUSTOM_COMPONENTS.clear() and CustomComponent.create().get_component.cache_clear()) since CustomComponent is gone.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["@rx.memo(fn)"] --> B{Return annotation?}
    B -->|rx.Component| C[_create_component_definition]
    B -->|rx.Var| D[_create_function_definition]
    B -->|other / missing| E[TypeError]
    C --> F[_analyze_params for_component=True]
    D --> G[_analyze_params for_component=False]
    F --> H{Per param: _classify_parameter}
    G --> H
    H -->|REST| I[MemoParamKind.REST]
    H -->|CHILDREN| J[MemoParamKind.CHILDREN]
    H -->|EVENT_TRIGGER| K[MemoParamKind.EVENT_TRIGGER]
    H -->|VALUE| L[MemoParamKind.VALUE]
    C --> M[_register_memo_definition]
    D --> M
    M --> N[EXPERIMENTAL_MEMOS dict]
    N --> O[compile_memo_components]
    O --> P[Per-memo .jsx module]
    O --> Q[Empty index.jsx]
    subgraph Call Site
        R["wrapper(*children, **kwargs)"] --> S[_MemoCallBinding]
        S --> T{bind_call_value per param}
        T -->|VALUE| U[_props dict camelCase]
        T -->|EVENT_TRIGGER| V[_event_triggers dict]
        T -->|CHILDREN/REST| W[pass-through to Component._post_init]
        U --> X[ExperimentalMemoComponent]
        V --> X
        W --> X
    end
Loading

Comments Outside Diff (1)

  1. packages/reflex-base/src/reflex_base/components/memo.py, line 1534-1542 (link)

    P2 MemoParam and MemoParamKind are used externally — reflex/compiler/utils.py imports MemoParamKind directly, and the new unit tests construct MemoParam objects explicitly — but neither symbol appears in __all__. Code that does from reflex_base.components.memo import * (e.g. reflex/components/memo.py) therefore won't re-export them, so any downstream caller that relies on reflex.components.memo.MemoParamKind would get an ImportError.

Reviews (1): Last reviewed commit: "chore(memo): annotate rx.memo return typ..." | Re-trigger Greptile

Comment on lines 828 to 833
for param in params:
placeholder = _placeholder_for_param(param)
if param.kind in (
placeholder = param.make_placeholder()
if param.parameter_kind in (
inspect.Parameter.POSITIONAL_ONLY,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No validation for missing required props at call site

_bind_value and _bind_event_trigger both silently no-op when the caller omits a declared parameter (if param.name in binding.raw_kwargs). For EVENT_TRIGGER params this is particularly sharp: defaults are explicitly rejected by _validate_event_trigger, making the param semantically required, yet omitting it at the call site produces no Python-level error. The missing prop becomes an undefined JS variable inside the rendered JSX, visible only as a broken interaction in the browser. Adding a param.default is inspect.Parameter.empty check before skipping — and raising TypeError when the param is required — would surface the mistake at the Python call site.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 15, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks


Comparing FarhanAliRaza:non-exp-memo (d5650cf) with main (9ed3692)

Open in CodSpeed

Restore reflex/experimental/memo.py as a thin module redirect to
reflex_base.components.memo so existing rx.experimental.memo imports
keep working with a deprecation warning.
Rename ExperimentalMemo* classes, EXPERIMENTAL_MEMOS registry, and
related helpers/tests to plain Memo*/MEMOS now that memo is no longer
experimental. Move integration and unit tests out of experimental/ to
mirror the new module location.
…efaults

Reusable Var-typed empty-value constants so memo signatures can spell
strict defaults without per-call-site `Var.create(...)` (which trips B008)
or bespoke module-level singletons. Updates the memo doc and the
in-tree memos that previously rolled their own empty Var.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant