From eee5f4cf8edee255d3f165d8893d2f7cfb9380e9 Mon Sep 17 00:00:00 2001 From: wantosure Date: Mon, 24 Aug 2026 21:43:52 +0800 Subject: [PATCH 1/2] Fix client state setter event var args --- reflex/experimental/client_state.py | 13 +++++++++---- tests/units/test_event.py | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index e24315b4734..f789640f6b4 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -22,6 +22,12 @@ } +def _event_arg_name(value_str: str) -> tuple[str, ...]: + """Return the event argument name for an event-derived expression.""" + match = re.match(r"^([A-Za-z_$][0-9A-Za-z_$]*)", value_str) + return (match.group(1),) if match else () + + def _client_state_ref(var_name: str) -> Var: """Get the ref accessor Var for a ClientStateVar. @@ -194,7 +200,7 @@ def create( @property def value(self) -> Var: - """Get a placeholder for the Var. + """A placeholder for the Var. This property can only be rendered on the frontend. @@ -235,8 +241,7 @@ def set_value(self, value: Any = NoValue) -> Var: value_str = str(value_var) setter = ArgsFunctionOperationBuilder.create( - # remove patterns of ["*"] from the value_str using regex - args_names=(re.sub(r"(\?\.)?\[\".*\"\]", "", value_str),) + args_names=_event_arg_name(value_str) if value_str.startswith("_") else (), return_expr=setter.call(value_var), @@ -294,4 +299,4 @@ def push(self, value: Any) -> EventSpec: msg = "ClientStateVar must be global to push the value." raise ValueError(msg) value = Var.create(value) - return run_script(f"{_client_state_ref(self._setter_name)}({value})") + return run_script(f"{_client_state_ref(self._setter_name)}({value})") \ No newline at end of file diff --git a/tests/units/test_event.py b/tests/units/test_event.py index be7fda5ad56..7b7ea7a1ea7 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -361,6 +361,20 @@ def test_event_window_alert(): ) +def test_call_function_client_state_setter_accepts_optional_chain_var(): + """Client state setters should use the event object as the function arg.""" + from reflex.experimental.client_state import ClientStateVar + + last_x = ClientStateVar.create("last_x", default=0) + spec = rx.call_function(last_x.set_value(Var("_event?.clientX"))) + + assert ( + format.format_event(spec) + == 'ReflexEvent("_call_function", {function:((_event) => ' + "(refs['_client_state_setLast_x'](_event?.clientX))),callback:null})" + ) + + @pytest.mark.parametrize( ("func", "qualname"), [("set_focus", "_set_focus"), ("blur_focus", "_blur_focus")] ) @@ -1237,4 +1251,4 @@ def f(s: GlobalState, arg: str): def test_decentralized_event_global_state(): """Test the decentralized event with a global state.""" _ = rx.input(on_change=f("foo")) - _ = rx.input(on_change=f) + _ = rx.input(on_change=f) \ No newline at end of file From a209a40ae3174e9ea036b5cb8ad087f695259bad Mon Sep 17 00:00:00 2001 From: wantosure Date: Mon, 24 Aug 2026 22:18:45 +0800 Subject: [PATCH 2/2] Handle unicode event argument names --- reflex/experimental/client_state.py | 44 +++++++++++++++++++++++++++-- tests/units/test_event.py | 14 +++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index f789640f6b4..e2045fc60d4 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -3,7 +3,7 @@ from __future__ import annotations import dataclasses -import re +import unicodedata from collections.abc import Callable from typing import Any @@ -24,8 +24,46 @@ def _event_arg_name(value_str: str) -> tuple[str, ...]: """Return the event argument name for an event-derived expression.""" - match = re.match(r"^([A-Za-z_$][0-9A-Za-z_$]*)", value_str) - return (match.group(1),) if match else () + if not value_str: + return () + + arg_name = value_str[0] + if not _is_js_identifier_start(arg_name): + return () + + for char in value_str[1:]: + if not _is_js_identifier_part(char): + break + arg_name += char + + return (arg_name,) + + +def _is_js_identifier_start(char: str) -> bool: + """Return whether a character can start a JavaScript identifier.""" + return char in {"$", "_"} or unicodedata.category(char) in { + "Lu", + "Ll", + "Lt", + "Lm", + "Lo", + "Nl", + } + + +def _is_js_identifier_part(char: str) -> bool: + """Return whether a character can continue a JavaScript identifier.""" + return ( + _is_js_identifier_start(char) + or unicodedata.category(char) + in { + "Mn", + "Mc", + "Nd", + "Pc", + } + or char in {"\u200c", "\u200d"} + ) def _client_state_ref(var_name: str) -> Var: diff --git a/tests/units/test_event.py b/tests/units/test_event.py index 7b7ea7a1ea7..699f1d9d73c 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -375,6 +375,20 @@ def test_call_function_client_state_setter_accepts_optional_chain_var(): ) +def test_call_function_client_state_setter_accepts_unicode_event_var(): + """Client state setters should preserve unicode event argument names.""" + from reflex.experimental.client_state import ClientStateVar + + last_x = ClientStateVar.create("last_x", default=0) + spec = rx.call_function(last_x.set_value(Var("_événement?.clientX"))) + + assert ( + format.format_event(spec) + == 'ReflexEvent("_call_function", {function:((_événement) => ' + "(refs['_client_state_setLast_x'](_événement?.clientX))),callback:null})" + ) + + @pytest.mark.parametrize( ("func", "qualname"), [("set_focus", "_set_focus"), ("blur_focus", "_blur_focus")] )