Skip to content

fix(match): bind useContext for state vars used in rx.match case conditions#6676

Open
picklelo wants to merge 1 commit into
mainfrom
picklelo/fix-match-condition-usecontext
Open

fix(match): bind useContext for state vars used in rx.match case conditions#6676
picklelo wants to merge 1 commit into
mainfrom
picklelo/fix-match-condition-usecontext

Conversation

@picklelo

Copy link
Copy Markdown
Contributor

Problem

rx.match(...) with component branches and a state Var used in a case condition raises a frontend ReferenceError: Can't find variable: reflex___state____state__... at render. The idiomatic "match on a literal subject" form triggers it:

rx.match(True, (State.x > 0, comp_a), (State.y > 0, comp_b), default)

Reported in #6675 (with a compiled-output trace).

Root cause

Match rendered its switch inline into the page component, and the per-case condition Vars live in match_cases (which is not a JS property), so they were never surfaced to the compiler. The page route emits state useContext bindings only through memoized child components, never inline — so the inlined switch referenced the substate context variable without ever binding it.

The Var-return branch of _create_match_cond_var_or_component already merges the case-condition var-data; the component-return branch did not, and Match had no _get_vars / memoization handling for the conditions.

Fix

  • _memoization_mode = MemoizationMode(recursive=False) — render Match inside a snapshot memo wrapper (mirrors Foreach, whose stateful render logic likewise belongs in the memo body), so the switch + conditions get an in-scope useContext binding instead of being inlined into the page component.
  • Override _get_vars to also yield the per-case condition Vars, so their hooks (the useContext bindings) are emitted.
  • add_imports now merges the case-condition var-data (mirrors the Var-return branch), so the StateContexts / useContext imports are present.

Verification

Compiled a minimal repro (rx.match(True, (S.a > 0, ...), (S.b > 0, ...), default)) with the patch:

  • The page route (_index.jsx) no longer references the substate var (previously the unbound reference).
  • A Match_comp_*.jsx memo wrapper is now generated that declares const ...state = useContext(StateContexts....) and contains the switch referencing the now-bound var.
  • Verified for both "state only in the conditions" and "state in conditions + branches (incl. a nested rx.cond)".
  • ruff check and ruff format --check pass.

Fixes #6675.

@picklelo picklelo requested a review from a team as a code owner June 23, 2026 18:09
@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes a ReferenceError: Can't find variable crash in rx.match when a state Var appears only in a case condition (not the subject) with component branches, by surfacing those condition Vars to the compiler so their useContext bindings and imports are emitted inside the memo wrapper.

  • _get_vars override yields the per-case condition Vars from match_cases (a non-JavaScript-property field skipped by the base walker), allowing the memoization system to detect statefulness even when the match subject is a literal like True.
  • add_imports expansion merges var data from all case-condition Vars (mirroring the existing Var-return branch in _create_match_cond_var_or_component), ensuring StateContexts/useContext imports are present in the emitted memo file.
  • A targeted regression test validates that rx.match(True, (State.x == ..., comp), ...) produces a memoized wrapper, preventing silent regressions.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to surfacing previously-invisible Vars from Match's case conditions and has a regression test that directly covers the broken pattern.

Both changes in match.py are additive and correct: _get_vars now exposes condition Vars that lived in a non-JS-property field (invisible to the base walker), and add_imports mirrors the var-data merge already done in the Var-return code path. The fix doesn't alter rendering logic, component structure, or any existing behavior for the already-working stateful-subject case. The regression test pins the literal-subject scenario so this cannot silently break again.

No files require special attention.

Important Files Changed

Filename Overview
packages/reflex-components-core/src/reflex_components_core/core/match.py Adds _get_vars override to surface case-condition Vars and expands add_imports to include condition var data; fixes ReferenceError for state Vars used only in case conditions.
tests/units/compiler/test_memoize_plugin.py Adds a targeted regression test verifying that Match with a literal subject but stateful case conditions is memoized; test uses an appropriate assertion on wrapper tag name.
packages/reflex-components-core/news/6675.bugfix.md Changelog entry accurately describing the bug and fix.

Reviews (2): Last reviewed commit: "fix(match): bind useContext for state va..." | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Jun 23, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing picklelo/fix-match-condition-usecontext (6ea5a31) with main (767c111)

Open in CodSpeed

Footnotes

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

…itions

rx.match only counted its subject toward statefulness, so a state Var
used only in a case condition with a literal subject -- e.g.
rx.match(True, (State.x > 0, comp), ...) -- left Match un-memoized. The
compiled switch then referenced the substate context variable without a
useContext binding, raising "ReferenceError: Can't find variable" at
render (the page route emits useContext only via memoized children).

Surface the case-condition Vars in Match._get_vars (and merge their
var-data in add_imports, mirroring the Var-return branch) so Match is
memoized when a case condition is stateful and the binding is emitted.
Keeps the existing passthrough strategy (branches still memoize
independently). Adds a regression test.

Fixes #6675.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@picklelo picklelo force-pushed the picklelo/fix-match-condition-usecontext branch from c4055e4 to 6ea5a31 Compare June 23, 2026 18:48
@masenf

masenf commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

This does seem to work, but now that memoization mode is MemoizationMode(recursive=False), the compiler will never memoize the stateful values returned by the match. I think what we need is to update the statefulness detection for rx.match so if any conditions are stateful, then it gets memoized while still allowing its children to be separately memoized if they are themselves stateful.

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.

rx.match: state vars in case conditions (component branches) emit unbound useContext → ReferenceError: Can't find variable: <substate>

2 participants