Skip to content

fix(compiler): check memo modules for state reads when state is disabled - #7630

Open
fixfon wants to merge 3 commits into
reflex-dev:mainfrom
fixfon:fix/7579-memo-state-guard
Open

fixfon wants to merge 3 commits into
reflex-dev:mainfrom
fixfon:fix/7579-memo-state-guard

Conversation

@fixfon

@fixfon fixfon commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

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

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

What

With rx.App(enable_state=False), compile_app is supposed to raise "To access rx.State in frontend components, at least one subclass of rx.State must be defined in the app." when a component reads state. The check ran right after the pages were compiled and only looked at page output. Auto-memoization moves most state reads into memo modules, and those are compiled later, together with app-wrap memos and the hydrate fallback memo. So the issue's example (rx.text(DynState.label)) compiled without error and failed at runtime.

The check now runs after compile_memo_components and scans every module in compile_results, which at that point holds the page modules and all memo modules. code_uses_state_contexts itself is unchanged.

Why this direction

I went for the smallest change that keeps the existing guard logic: same predicate, same error, just run over all the compiled JS that can subscribe to state instead of the pages only.

Alternatives I considered:

  • Delete the guard (option 1 in the issue). As noted in the issue, the unsupported state use would then only fail later, at runtime.
  • Collect VarData.state from the compiled hooks (option 2). As @masenf pointed out, this misses the reported example, because _get_vars_hooks keeps only position metadata in the hook entries.
  • Structural validation from the originating Vars across pages, memos, app wraps and the hydrate fallback. This is the more robust fix, but it is a much larger change. It can still be done later; this PR doesn't get in its way.

Known limits, unchanged by this PR: the check still matches the useStateContext( substring, so literal text containing it is still a false positive (now in memo modules too). One behavior change: MEMOS is compiled as a whole, so an @rx.memo that reads state is now rejected in an enable_state=False app even if no page renders it. Before, it was emitted but never imported.

Tests

New cases in tests/units/compiler/test_compiler.py, using the real compile_app(app, dry_run=True, use_rich=False) path:

  • enable_state=False with a state read raises for: an auto-memoized page component, a non-memoized page component, an rx.memo body, an app wrap, and the hydrate fallback.
  • The same five compile when state is enabled.
  • A stateless app with enable_state=False compiles, in dev and in prod.

Without the fix, 4 cases fail with DID NOT RAISE ReflexRuntimeError (auto memo, explicit memo, app wrap, hydrate fallback); the non-memoized page case already raised. With the fix, all 12 pass. The new tests cover every changed line in compile_app.

Also ran test_var.py::test_code_uses_state_contexts, test_compiler.py, test_memoize_plugin.py and test_app.py (434 passed), and ruff check, ruff format --check, pyright and pre-commit on the changed files.

closes #7579

View guided diff

The enable_state=False guard only scanned page modules, but auto-memoization
moves state reads into memo modules that are compiled afterwards. Run the guard
once the memo modules exist so reads in memoized components, rx.memo functions,
app wraps and the hydrate fallback are rejected too.

Fixes reflex-dev#7579
@fixfon
fixfon requested a review from a team as a code owner October 22, 2026 00:11

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

No issues found across 3 files

View guided diff | Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no blocking issues remain.

Summary

compile_app now checks generated memo modules and the app root for state reads when state is disabled.

  • The compiler catches state reads in every generated module.

Reviews (3) · Last reviewed commit: "test(compiler): start the state guard te..." · Reviewed by Greptile

Comment thread reflex/compiler/compiler.py Outdated
@masenf masenf added the bug Something isn't working label Oct 22, 2026 — with ChatGPT Codex Connector
…isabled

Wraps with MemoizationDisposition.NEVER keep their state reads in the app root,
which compile_app_root produced after the guard ran. Run the guard after the
app root is compiled and cover it with an unmemoized app wrap test.
@codspeed

codspeed Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning

⚠️ 12 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 148 untouched benchmarks


Comparing fixfon:fix/7579-memo-state-guard (1617db0) with main (656e8c7)

Open in CodSpeed

Memos that other modules define at import time can read state, and every
registered memo is compiled. In a full unit run they tripped the guard in the
stateless memo tests. Clear the memo table in isolated_compile.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

enable_state=False guard (code_uses_state_contexts) misses memoized state reads

2 participants