Skip to content

Commit 8fb2fb4

Browse files
fix: set_focus/blur_focus args stripped by get_handler_args for stateless handlers (#6314)
get_handler_args() used `len(args) > 1` assuming all handlers have a `self` parameter from a State class. Server-side events like _set_focus have no state, so their single `ref` parameter was incorrectly stripped, producing an empty payload and a frontend TypeError: Cannot read properties of undefined (reading ref).
1 parent 55191ba commit 8fb2fb4

File tree

3 files changed

+56
-1
lines changed

3 files changed

+56
-1
lines changed

packages/reflex-base/src/reflex_base/event/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2096,8 +2096,9 @@ def get_handler_args(
20962096
The handler args.
20972097
"""
20982098
args = event_spec.handler._parameters
2099+
n_self_args = 1 if event_spec.handler.state is not None else 0
20992100

2100-
return event_spec.args if len(args) > 1 else ()
2101+
return event_spec.args if len(args) > n_self_args else ()
21012102

21022103

21032104
def fix_events(

tests/integration/test_server_side_event.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def index():
4949
rx.input(default_value="a", id="a"),
5050
rx.input(default_value="b", id="b"),
5151
rx.input(default_value="c", id="c"),
52+
rx.el.input(name="name", id="focus_target"),
5253
rx.button(
5354
"Clear Immediate",
5455
id="clear_immediate",
@@ -78,6 +79,11 @@ def index():
7879
id="clear_return_c",
7980
on_click=SSState.set_value_return_c,
8081
),
82+
rx.el.button(
83+
"Focus input",
84+
id="focus_input",
85+
on_click=rx.set_focus("focus_target"),
86+
),
8187
)
8288

8389

@@ -183,3 +189,24 @@ def test_set_value_return_c(driver):
183189
assert input_a.get_attribute("value") == "a"
184190
assert input_b.get_attribute("value") == "b"
185191
assert input_c.get_attribute("value") == ""
192+
193+
194+
def test_set_focus(driver):
195+
"""Call set_focus and verify the target input becomes active.
196+
197+
Args:
198+
driver: selenium WebDriver open to the app
199+
"""
200+
input_target = driver.find_element(By.ID, "focus_target")
201+
btn = driver.find_element(By.ID, "focus_input")
202+
203+
assert input_target
204+
assert btn
205+
206+
btn.click()
207+
208+
assert AppHarness.poll_for_or_raise_timeout(
209+
lambda: (
210+
driver.execute_script("return document.activeElement?.id") == "focus_target"
211+
)
212+
)

tests/units/test_event.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,33 @@ def test_focus(func: str, qualname: str):
280280
)
281281

282282

283+
@pytest.mark.parametrize(
284+
("func", "qualname"), [("set_focus", "_set_focus"), ("blur_focus", "_blur_focus")]
285+
)
286+
def test_focus_event_chain_preserves_args(func: str, qualname: str):
287+
"""Test that set_focus/blur_focus ref arg survives EventChain.create.
288+
289+
Args:
290+
func: The event function name.
291+
qualname: The sig qual name passed to JS.
292+
"""
293+
spec = getattr(event, func)("input1")
294+
chain = EventChain.create(value=spec, args_spec=lambda: ())
295+
assert isinstance(chain, EventChain)
296+
assert len(chain.events) == 1
297+
chain_spec = chain.events[0]
298+
assert isinstance(chain_spec, EventSpec)
299+
# The ref arg must survive the EventChain pipeline.
300+
assert len(chain_spec.args) == 1
301+
assert chain_spec.args[0][0].equals(Var(_js_expr="ref"))
302+
assert chain_spec.args[0][1].equals(LiteralVar.create("ref_input1"))
303+
# Verify the serialized output includes the ref in the payload.
304+
assert (
305+
format.format_event(chain_spec)
306+
== f'ReflexEvent("{qualname}", {{ref:"ref_input1"}})'
307+
)
308+
309+
283310
def test_set_value():
284311
"""Test the event window alert function."""
285312
spec = event.set_value("input1", "")

0 commit comments

Comments
 (0)