Repository navigation
Tighten the rx.client_state contract before 0.11.0 - #7804
Conversation
rx.client_state is new public API in 0.11, so make its contract strict and
its errors precise (FINDING-034, FINDING-035 of the 0.11.0a1 QA run).
- The "not global" error and the ClientStateVar.create docstring show
rx.client_state(default, name="my_name"); the old hint passed the name
as the default and reproduced the error. When an unnamed var's default
is an identifier-like string (the name-first order of the deprecated
API), the error says so and shows the keyword call.
- Generated JS bindings are the name plus a fixed suffix each: getter
<name>RxClientState, setter <name>RxClientStateSet, updater placeholder
prev<N>RxClientStateArg. They are injective in the name and pairwise
disjoint, so item/Item no longer both declare setItem, and a var named
prev0 cannot be shadowed by an updater's parameter.
- A name must be an ASCII identifier without '$' ('$' is reserved for
generated names, so name="cs$0" can no longer alias an unnamed var);
the error names the bad value and the rule. The deprecated
rx._x.client_state / ClientStateVar.create apply the same rule and keep
their argument order and warnings.
- One name declared with two different defaults fails the compile,
naming both defaults and their file:line, instead of the first mounted
declaration silently winning (or, with auto_memoize=False, a duplicate
const declaration taking down every route). The check runs where hooks
are rendered, across every body of one App._compile, so creating vars
never raises and a hot reload with an edited default compiles. Identical
re-declarations stay allowed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
A name declared with two different defaults now raises ClientStateConflictError (ReflexError, ValueError), new in reflex_base.utils.exceptions, like the compiler's other app-level compile errors. The message is unchanged; name-rule errors stay ValueError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
[Medium impact] The PR appears safe to merge, with a non-blocking memory-retention issue still open from the previous review. SummaryTightens the
Reviews (3) · Last reviewed commit: "Address review: multiline defaults, boun..." · Reviewed by Greptile |
There was a problem hiding this comment.
All reported issues were addressed across 12 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
Merging this PR will degrade performance by 4.16%2 warnings
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing |
|
CodSpeed is red here ( Generated by Claude Code |
- Match `useClientState` declarations with re.DOTALL, so a default whose javascript spans lines is still compared for conflicts. - Record a declaration site only for chosen names; a generated name (legacy unnamed global vars) is unique and cannot conflict, and added an entry per construction. - Give the app-level client state tests a clean registration context so the patched config is the one the compile reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
* Tighten the rx.client_state contract before 0.11.0
rx.client_state is new public API in 0.11, so make its contract strict and
its errors precise (FINDING-034, FINDING-035 of the 0.11.0a1 QA run).
- The "not global" error and the ClientStateVar.create docstring show
rx.client_state(default, name="my_name"); the old hint passed the name
as the default and reproduced the error. When an unnamed var's default
is an identifier-like string (the name-first order of the deprecated
API), the error says so and shows the keyword call.
- Generated JS bindings are the name plus a fixed suffix each: getter
<name>RxClientState, setter <name>RxClientStateSet, updater placeholder
prev<N>RxClientStateArg. They are injective in the name and pairwise
disjoint, so item/Item no longer both declare setItem, and a var named
prev0 cannot be shadowed by an updater's parameter.
- A name must be an ASCII identifier without '$' ('$' is reserved for
generated names, so name="cs$0" can no longer alias an unnamed var);
the error names the bad value and the rule. The deprecated
rx._x.client_state / ClientStateVar.create apply the same rule and keep
their argument order and warnings.
- One name declared with two different defaults fails the compile,
naming both defaults and their file:line, instead of the first mounted
declaration silently winning (or, with auto_memoize=False, a duplicate
const declaration taking down every route). The check runs where hooks
are rendered, across every body of one App._compile, so creating vars
never raises and a hot reload with an edited default compiles. Identical
re-declarations stay allowed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
* Raise client state declaration conflicts as a ReflexError
A name declared with two different defaults now raises
ClientStateConflictError (ReflexError, ValueError), new in
reflex_base.utils.exceptions, like the compiler's other app-level compile
errors. The message is unchanged; name-rule errors stay ValueError.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
* Address review: multiline defaults, bounded sites, test config isolation
- Match `useClientState` declarations with re.DOTALL, so a default whose
javascript spans lines is still compared for conflicts.
- Record a declaration site only for chosen names; a generated name
(legacy unnamed global vars) is unique and cannot conflict, and added
an entry per construction.
- Give the app-level client state tests a clean registration context so
the patched config is the one the compile reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
---------
Co-authored-by: Claude <noreply@anthropic.com>
(cherry picked from commit 96de6fe)
…the train Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dxjadq3qkhKnqZR2b9WBFi
Fixes FINDING-034 and FINDING-035 from the reflex 0.11.0a1 pre-release testing (artifacts on branch
claude/vibrant-pasteur-3921g3,prerelease-testing/runs/2026-11-11-v0.11.0a1/client_vars/andverify_cv/).All Submissions:
Type of change
$in names is rejected; one name with two defaults fails the compile)Changes To Core Features:
Defects
rx.client_stateis new public API in 0.11 (#6936); this makes its contract strict before 0.11.0 ships.ClientStateVar.createdocstring showedrx.client_state("my_name"), which creates an unnamed var whose default is"my_name"(following the hint raised the same error).name="item"andname="Item"both declaredsetItem(Identifier setItem has already been declared: the memo module 500s in dev, the prod build fails); one name declared with two different defaults was silently resolved by whichever mounted first, or withauto_memoize=False500'd every route (Identifier openRxClientState has already been declared). Nothing was checked in Python;name="cs$0"was accepted and clashed with the first unnamed var.Fix
rx.client_state(default, name="my_name"). When an unnamed var's default looks like a name (the deprecated API's name-first order), the error raised bypush/retrieve/global_value/global_setsays so. Signature unchanged (namewas already keyword-only).<name>RxClientState, setter<name>RxClientStateSet, updater placeholderprev<N>RxClientStateArg— injective in the name and mutually distinct (the old placeholder could also shadow a var namedprev0).[A-Za-z_][A-Za-z0-9_]*(ASCII identifier without$, which is reserved for generated names);prefixuses the same validator; the error names the bad value. Every name in the docs passes. The deprecatedrx._x.client_state/ClientStateVar.createapply the same rule and keep their argument order and deprecation warnings.ClientStateConflictError(a newReflexError/ValueErrorsubclass inreflex_base.utils.exceptions) at compile time, naming both declarations with file:line. The check runs as hooks are rendered (templates._render_hooks), across oneApp._compile(a fresh record per compile), so creating a var never raises and a dev hot reload with an edited default works. Identical re-declarations stay allowed (the documented "any component can read and write it" pattern).docs/wrapping-react/overview.mdon the name rule and the same-default rule.Before / after:
item+Item:setItemdeclared twice →itemRxClientState, itemRxClientStateSetandItemRxClientState, ItemRxClientStateSet.rx.client_state("x", name="a-b")→name 'a-b' is not a valid client state name: it must be an ASCII identifier, a letter or underscore followed by letters, digits or underscores. It must not contain '$', which is reserved for generated names.name="dup"with different defaults →Client state var 'dup' is declared with two different defaults: "dup-default" (declared at …/shared.py:15) and "OTHER-default" (declared at …/other_mod.py:6). …Test plan
tests/units/reflex_base/test_client_state.py,tests/units/experimental/test_client_state.py,tests/units/test_app.py: 17 fail onmain(hint ×4 accessors, docstring, name-as-default hint, case-only identifiers, no duplicateconstin the compiled memo module, placeholder shadowing, name rejection ×4, non-string message, same-body conflict, legacy name rule, app-level two-page conflict naming both sites), e.g.assert 3 == 4 … {'ItemRxClientState','itemRxClientState','setItem'},DID NOT RAISE ValueError; allow-path tests (identical declarations, documented names, legacy positional name, conflict record not outliving a compile) pass before and after. Existing setter/placeholder spellings updated in ~20 assertions (test_client_state.py,test_memoize_plugin.py).ClientStateConflictErrorchange.ruff,pyright, pre-commit: clean./casing2used to 500), also withauto_memoize=False; hot reload with an edited default recompiles; the duplicate-default app fails at startup with the error above, the same-default variant renders all 5 routes; reflex-examplesoverkey(deprecatedClientStateVar.create,.set_value) 6/6 with its deprecation warning.Notes
.set(value)/push(value)are not type-checked against the var's type;prefixis ignored whennameis given (documented); defaults that render differently (0vs0.0) count as a conflict; types are not compared.News fragments:
packages/reflex-base/news/+client-state-contract.bugfix.md,news/+client-state-contract.bugfix.md.Generated by Claude Code