Collect app wraps from the whole @rx.memo body

Merged
MA
1mo ago
+476-161

closes #7174

A memo's body compiles into its own JS module, so the call site is represented in the page tree only by a generated wrapper component and the body subtree is invisible to every app-wrap collector. The wrapper forwarded exactly one thing: the static _get_app_wrap_components of the body's root class, copied onto the wrapper class by _get_memo_component_class.

Everything else was dropped:

  • A provider requested by a nested descendant never reached the app root, so rx.box(NeedsProvider()) lost what NeedsProvider() kept.
  • Providers requested through VarData.app_wraps were dropped at any depth, including the root โ€” which is how rx.upload asks for UploadFilesProvider. useContext(UploadFilesContext) then resolved to its default value and file selection silently did nothing.
  • Components whose create() returns a wrapper element lost theirs too: rx.data_editor returns a Div, so its portal app wrap went missing even as the memo's root.

Not specific to any one component โ€” it affects anything requesting an app wrap, including third-party and reflex-enterprise components (the drag-and-drop provider is the case that surfaced it).

Changes

Each wrapper class now gets a _get_app_wrap_components that reports what the body it stands in for requires, class-declared and var-declared alike. A distinct function per class is load-bearing: the page collector dedupes its scan by type(comp)._get_app_wrap_components identity, so a shared function would let only the first memo on a page contribute. The result is computed once per class, and reset_memo_component_classes() rebuilds classes each compile, so it can't go stale.

Passthrough wrappers still report only their root โ€” their descendants render at the call site, where the page walk already collects them.

Routing this through _get_app_wrap_components fixes both collection surfaces at once: the compiler plugin walk for pages, and Component._get_all_app_wrap_components, which App._app_root uses to expand the app-wrap chain (a chain that itself contains memo components โ€” the toaster provider, the sticky badge).

The var-app-wrap collectors move from reflex.compiler.plugins.builtin to a new reflex_base.components.app_wraps so memo.py can share them instead of duplicating the walk. They only ever touched Component and VarData, never the compiler, so this is a relocation with no behavior change; builtin.py, memoize.py and compiler.py import from the new home.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

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?

Regression tests in tests/units/components/test_memo.py cover nested and root class-declared wraps, var-declared wraps (rx.upload inside a memo), the per-class function identity the collector's dedupe depends on, and the _get_all_app_wrap_components path. tests/units/test_app.py adds the end-to-end page-compile assertion.

Verified locally: uv run pytest tests/units passes (77.53% coverage; the only failures are the pre-existing tests/units/reflex_cli ones, unchanged on a clean tree), uv run ruff check ., uv run ruff format . and uv run pyright reflex tests are clean, and scripts/make_pyi.py produces no changes.

๐Ÿค– Generated with Claude Code

https://claude.ai/code/session_01FFRsBUWSsWQCPKPTZ8yxT1


Generated by Claude Code