浏览代码

Support event_actions in Form `on_submit` (#4912)

Masen Furer 2 月之前
父节点
当前提交
4bc26bf07a

+ 1 - 1
reflex/components/el/elements/forms.py

@@ -32,7 +32,7 @@ HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string(
         ev.preventDefault()
         const {{ form_data }} = {...Object.fromEntries(new FormData($form).entries()), ...{{ field_ref_mapping }}};
 
-        ({{ on_submit_event_chain }}());
+        ({{ on_submit_event_chain }}(ev));
 
         if ({{ reset_on_submit }}) {
             $form.reset()

+ 1 - 1
reflex/components/el/elements/forms.pyi

@@ -17,7 +17,7 @@ from .base import BaseHTML
 
 FORM_DATA = Var(_js_expr="form_data")
 HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string(
-    "\n    const handleSubmit_{{ handle_submit_unique_name }} = useCallback((ev) => {\n        const $form = ev.target\n        ev.preventDefault()\n        const {{ form_data }} = {...Object.fromEntries(new FormData($form).entries()), ...{{ field_ref_mapping }}};\n\n        ({{ on_submit_event_chain }}());\n\n        if ({{ reset_on_submit }}) {\n            $form.reset()\n        }\n    })\n    "
+    "\n    const handleSubmit_{{ handle_submit_unique_name }} = useCallback((ev) => {\n        const $form = ev.target\n        ev.preventDefault()\n        const {{ form_data }} = {...Object.fromEntries(new FormData($form).entries()), ...{{ field_ref_mapping }}};\n\n        ({{ on_submit_event_chain }}(ev));\n\n        if ({{ reset_on_submit }}) {\n            $form.reset()\n        }\n    })\n    "
 )
 ButtonType = Literal["submit", "reset", "button"]
 

+ 52 - 1
tests/integration/test_event_actions.py

@@ -8,12 +8,17 @@ from typing import Callable, Coroutine, Generator
 
 import pytest
 from selenium.webdriver.common.by import By
+from selenium.webdriver.common.keys import Keys
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.wait import WebDriverWait
 
 from reflex.testing import AppHarness, WebDriver
 
 
 def TestEventAction():
     """App for testing event_actions."""
+    from typing import Any
+
     import reflex as rx
 
     class EventActionState(rx.State):
@@ -32,6 +37,10 @@ def TestEventAction():
         def on_click_debounce(self):
             self.order.append("on_click_debounce")
 
+        @rx.event
+        def on_submit(self, form_data: dict[str, Any]):
+            self.order.append("on_submit")
+
     class EventFiringComponent(rx.Component):
         """A component that fires onClick event without passing DOM event."""
 
@@ -152,10 +161,26 @@ def TestEventAction():
                 ),
             ),
             on_click=EventActionState.on_click("outer"),  # pyright: ignore [reportCallIssue]
+        ), rx.form(
+            rx.dialog.root(
+                rx.dialog.trigger(
+                    rx.button("Open Dialog", type="button", id="btn-dialog"),
+                    on_click=rx.stop_propagation,  # pyright: ignore [reportArgumentType]
+                ),
+                rx.dialog.content(
+                    rx.dialog.close(
+                        rx.form(
+                            rx.button("Submit", id="btn-submit"),
+                            on_submit=EventActionState.on_submit.stop_propagation,  # pyright: ignore [reportCallIssue]
+                        ),
+                    ),
+                ),
+            ),
+            on_submit=EventActionState.on_submit,  # pyright: ignore [reportCallIssue]
         )
 
     app = rx.App(_state=rx.State)
-    app.add_page(index)
+    app.add_page(index)  # pyright: ignore [reportArgumentType]
 
 
 @pytest.fixture(scope="module")
@@ -332,3 +357,29 @@ async def test_event_actions_throttle_debounce(
         await poll_for_order(
             ["on_click_throttle"] * (exp_events - 1) + ["on_click_debounce"]
         )
+
+
+@pytest.mark.usefixtures("token")
+@pytest.mark.asyncio
+async def test_event_actions_dialog_form_in_form(
+    driver: WebDriver,
+    poll_for_order: Callable[[list[str]], Coroutine[None, None, None]],
+):
+    """Click links and buttons and assert on fired events.
+
+    Args:
+        driver: WebDriver instance.
+        poll_for_order: function that polls for the order list to match the expected order.
+    """
+    open_dialog_id = "btn-dialog"
+    submit_button_id = "btn-submit"
+    wait = WebDriverWait(driver, 10)
+
+    driver.find_element(By.ID, open_dialog_id).click()
+    el = wait.until(EC.element_to_be_clickable((By.ID, submit_button_id)))
+    el.click()  # pyright: ignore[reportAttributeAccessIssue]
+    el.send_keys(Keys.ESCAPE)  # pyright: ignore[reportAttributeAccessIssue]
+
+    btn_no_events = wait.until(EC.element_to_be_clickable((By.ID, "btn-no-events")))
+    btn_no_events.click()
+    await poll_for_order(["on_submit", "on_click:outer"])