Skip to content

feat: add app_wraps to VarData for Var-driven provider injection#6447

Open
FarhanAliRaza wants to merge 2 commits intoreflex-dev:mainfrom
FarhanAliRaza:app-wrap-vardata
Open

feat: add app_wraps to VarData for Var-driven provider injection#6447
FarhanAliRaza wants to merge 2 commits intoreflex-dev:mainfrom
FarhanAliRaza:app-wrap-vardata

Conversation

@FarhanAliRaza
Copy link
Copy Markdown
Contributor

Lets Vars declare app-level wrapper components in their VarData so the compiler can mount providers (state context, event loop, upload, etc.) based on what's actually used, instead of relying on hardcoded chains or special-case logic. State/event-loop providers now ride along on VarData.from_state and the events-hook helper, and UploadFilesProvider is mounted when selected_files/upload_file is referenced — even without an Upload component on the page. Layout renders the assembled chain so AppWrap reduces to hooks + children.

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.

  • New feature (non-breaking change which adds functionality)

  • 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?

Lets Vars declare app-level wrapper components in their VarData so the
compiler can mount providers (state context, event loop, upload, etc.)
based on what's actually used, instead of relying on hardcoded chains
or special-case logic. State/event-loop providers now ride along on
VarData.from_state and the events-hook helper, and UploadFilesProvider
is mounted when selected_files/upload_file is referenced — even without
an Upload component on the page. Layout renders the assembled chain so
AppWrap reduces to hooks + children.
@FarhanAliRaza FarhanAliRaza requested a review from a team as a code owner May 4, 2026 10:29
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 4, 2026

Merging this PR will degrade performance by 20.3%

❌ 5 regressed benchmarks
✅ 12 untouched benchmarks
⏩ 2 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_compile_page_single_pass[_stateful_page] 13.3 ms 16.3 ms -18.47%
test_compile_single_pass_all_artifacts[_stateful_page] 13 ms 16 ms -18.52%
test_evaluate_page_single_pass[_stateful_page] 7.3 ms 7.8 ms -6.28%
test_compile_page_full_context[_stateful_page] 15.9 ms 20 ms -20.3%
test_evaluate_page[_stateful_page] 6.5 ms 7 ms -7.07%

Comparing FarhanAliRaza:app-wrap-vardata (22e039e) with main (70ab07e)

Open in CodSpeed

Footnotes

  1. 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.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 4, 2026

Greptile Summary

This PR replaces the hardcoded StateProvider/EventLoopProvider JSX in the JS Layout template with a Var-driven mechanism: VarData gains an app_wraps field, allowing any Var (state vars, event chains, upload helpers) to declare which React providers it needs. The compiler collects these declarations during the page walk and assembles the provider chain dynamically, so UploadFilesProvider is mounted when selected_files/upload_file is used even without an explicit Upload component.

All P2 findings — a missing type annotation on an internal helper and a tag-only equality semantics note — are non-blocking.

Confidence Score: 4/5

Safe to merge; all findings are P2 style/latent concerns with no current-code defects.

Only P2 issues found: an Any type annotation on an internal private function and a tag-based equality design note that is intentional for current stateless providers. The core logic — VarData promotion in __init__, dedup in merge, fixpoint collection in _resolve_app_wrap_components, and snapshot-boundary handling in the memoize plugin — is correct and well-tested.

packages/reflex-base/src/reflex_base/vars/base.py (_identity_key equality semantics) and reflex/compiler/plugins/builtin.py (_ingest_var_data_app_wraps type annotation).

Important Files Changed

Filename Overview
packages/reflex-base/src/reflex_base/vars/base.py Adds app_wraps field to VarData, overrides __eq__/__hash__ using a tag-based identity key; equality for app_wraps entries is keyed on (priority, tag) only, which could silently deduplicate differently-configured providers sharing the same tag.
packages/reflex-base/src/reflex_base/components/state_context.py New module defining StateContextProvider, EventLoopContextProvider, and get_events_hooks_var_data() factory; clean, well-documented, fresh-instance-per-call design avoids deepcopy cache-leakage issues.
reflex/compiler/plugins/builtin.py Adds collect_var_app_wraps_in_subtree, collect_var_app_wraps_for_component, and _ingest_* helpers; _ingest_var_data_app_wraps accepts var_data: Any instead of VarData, reducing type safety.
packages/reflex-components-core/src/reflex_components_core/core/upload.py Converts module-level upload_files_context_var_data constant to get_upload_files_context_var_data() factory that carries UploadFilesProvider as an app_wrap; removes the now-redundant Upload._get_app_wrap_components override.
packages/reflex-base/src/reflex_base/compiler/templates.py Moves StateProvider/EventLoopProvider hardcoded JSX from Layout to the dynamic render chain; AppWrap now just hosts hooks and returns children, making the JS template provider-agnostic.
reflex/compiler/compiler.py Adds fixpoint iteration in _resolve_app_wrap_components to surface Var-declared app_wraps from the app-wrap chain itself; well-guarded against infinite loops by tracking newly added keys.
reflex/compiler/plugins/memoize.py Correctly surfaces Var-declared app_wraps from snapshot-boundary subtrees before they are sealed by MemoizeStatefulPlugin, preventing providers from being missed behind memoization boundaries.
packages/reflex-components-core/src/reflex_components_core/base/app_wrap.py Refactors AppWrap from a Fragment subclass to a standalone Component with library=None and an explicit tag; correctly models it as the local JS function defined in app_root_template.

Sequence Diagram

sequenceDiagram
    participant V as Var/VarData
    participant C as Component
    participant CP as DefaultCollectorPlugin
    participant MP as MemoizeStatefulPlugin
    participant R as _resolve_app_wrap_components
    participant T as app_root_template

    V->>V: VarData(app_wraps=[(priority, Provider)])
    C->>CP: _get_vars() / _get_hooks_internal()
    CP->>CP: collect_var_app_wraps_for_component()
    CP->>CP: _ingest_var_data_app_wraps() → page_app_wrap_components
    MP->>CP: collect_var_app_wraps_in_subtree() (snapshot boundary)
    CP->>R: page_app_wrap_components
    R->>R: merge Component._get_app_wrap_components()
    R->>R: fixpoint: collect app_wraps from wrapper subtrees
    R->>T: ordered wrap chain (priority desc)
    T->>T: Layout renders StateProvider > EventLoopProvider > ... > AppWrap(hooks + children)
Loading

Reviews (1): Last reviewed commit: "feat: add app_wraps to VarData for Var-d..." | Re-trigger Greptile

Comment on lines +95 to +96
def _ingest_var_data_app_wraps(
wraps_by_key: dict[tuple[int, str], Component],
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 Untyped var_data parameter reduces type safety

var_data is annotated as Any but the function immediately accesses .app_wraps and .hooks. A non-VarData argument silently passes type-checking and will raise AttributeError at runtime. All call sites pass VarData, so the annotation should match.

Comment on lines 284 to 292
@@ -267,6 +287,7 @@ def merge(*all: VarData | None) -> VarData | None:
deps=deps,
position=position,
components=components,
app_wraps=tuple(app_wraps_list),
)

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 app_wraps equality keyed only on (priority, tag) — different configs silently deduplicate

_identity_key compares app_wraps entries by (priority, component.tag or type(component).__name__), so two providers sharing the same tag and priority are considered equal even when their props differ (e.g., a pre-configured UploadFilesProvider with a custom max_file_size would be silently dropped if a plain one already exists at that priority). The design is intentional for the current stateless providers, but the asymmetry with components — which uses id(component) for strict identity — means future wrappers with meaningful props could be silently misconfigured without any error.

Comment thread tests/units/test_app.py
Comment on lines 2053 to 2058
"postcss-import": {},
autoprefixer: {},
},
};
""",
};
""",
)
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 Unintentional tab characters introduced in the postcss fixture string. The }; and the closing """ delimiter now carry a leading tab, producing inconsistent indentation in the written postcss.config.js stub and trailing whitespace after the closing brace. Postcss is whitespace-agnostic so tests still pass, but it differs from the rest of the file's style.

Suggested change
"postcss-import": {},
autoprefixer: {},
},
};
""",
};
""",
)
"postcss-import": {},
autoprefixer: {},
},
};
""",
)

EventLoopProvider now populates a module-level addEvents in
$/utils/context, so JSX literals constructed outside the React-tree
hoist path (e.g. ErrorBoundary.onError) can dispatch events without
useContext(EventLoopContext) being lexically in scope. State and
event-loop providers ride along on event-invocation VarData.app_wraps
(and via _get_event_app_wraps) so they still mount in the app root.
connectErrors stays on useContext since it drives re-renders;
AppWrap is now a Fragment rendering the chain in its body.
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