Skip to content

enable_state=False guard (code_uses_state_contexts) misses memoized state reads #7579

Open

Description

@FarhanAliRaza

Summary

code_uses_state_contexts (reflex/state.py) is a compile-time guard for rx.App(enable_state=False). It searches the generated page JS for the substring useContext(StateContexts. If it finds the substring, compile_app raises:

To access rx.State in frontend components, at least one subclass of rx.State must be defined in the app.

The guard is blind in the common case. Auto-memoization moves state reads out of the page module into memo modules. The guard checks only page output, so it does not see these reads.

Reproduce (main)

import reflex as rx


class DynState(rx.State):
    label: str = "x"


def index() -> rx.Component:
    return rx.text(DynState.label)


app = rx.App(enable_state=False)
app.add_page(index, route="/")
reflex compile --dry
  • Expected: ReflexRuntimeError ("To access rx.State in frontend components ...").
  • Actual: Success: App compiled successfully. The app then fails at runtime.

Why it matters

  • The guard depends on the exact text of a generated hook. Any change to the hook string breaks it without a test failure. fix: flatten providers for large state trees #7441 must change the substring to useStateContext( only to keep this guard in sync.
  • It gives a false sense of safety. It fires only for the few state reads that stay in the page module.

Proposal

Pick one:

  1. Delete code_uses_state_contexts, its call site in compile_app, and its tests. Since auto-memoization, the guard almost never fires.
  2. Replace it with a structural check: collect VarData.state from the compiled hooks of pages and memo components. Raise when any state is referenced and app._state is None. Do not match substrings of generated JS.

Activity

  1. linear-code commented on Oct 15, 2026

    @linear-code
  2. masenf commented on Oct 15, 2026

    @masenf
    Collaborator

    Confirmed through the real compiler API, compile_app(app, dry_run=True, use_rich=False), on main 7a1de653fb9746a5d905ac422eb5bec3f3219ba4 and #7441 head 3084bc4828cafc36ee8bfd1ec80306be5982e1e1. The reported example returns True with app._state is None on both. The state read is emitted in a memo, while both page outputs miss the guard. The same read with state enabled, and a genuinely static page with state disabled, compile successfully. These checks exercised compiler output; they did not validate the CLI or browser runtime.

    Two additional observations:

    1. The string check also rejects a completely stateless page. On the tested main revision, this app raises ReflexRuntimeError through the compiler API:
    import reflex as rx
    
    app = rx.App(enable_state=False)
    app.add_page(lambda: rx.text("useContext(StateContexts"), route="/")
    1. Collecting VarData.state from compiled hooks alone still misses the reported example. Its emitted memo hook has state == "". Component._get_vars_hooks replaces the originating VarData with position-only metadata when building hook entries. The dependency exists earlier in the originating VarData, as created by VarData.from_state.

    I recommend structural validation that collects dependencies from originating Vars during component/memo compilation, or deliberately preserves that metadata, before JS emission. Include pages, explicit and automatic memos, prop-embedded components, app wraps and hydrate fallbacks. The current guard runs before app-wrap and fallback memos are registered, so moving the check to all final compilation domains matters too.

    Regression coverage should include the missed memo read, nonmemoized reads, literal text containing either hook spelling, and valid stateless/client-only-hook and state-enabled controls. The diagnostic should explain disabled state: the original reproduction already defines a State subclass. Removing the guard would leave the unsupported state use to fail later.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions