diff --git a/integration/test_form_submit.py b/integration/test_form_submit.py
index ef5631cd7..eab0253ca 100644
--- a/integration/test_form_submit.py
+++ b/integration/test_form_submit.py
@@ -1,4 +1,5 @@
"""Integration tests for forms."""
+import functools
import time
from typing import Generator
@@ -10,8 +11,12 @@ from reflex.testing import AppHarness
from reflex.utils import format
-def FormSubmit():
- """App with a form using on_submit."""
+def FormSubmit(form_component):
+ """App with a form using on_submit.
+
+ Args:
+ form_component: The str name of the form component to use.
+ """
import reflex as rx
class FormState(rx.State):
@@ -32,7 +37,7 @@ def FormSubmit():
is_read_only=True,
id="token",
),
- rx.form.root(
+ eval(form_component)(
rx.vstack(
rx.chakra.input(id="name_input"),
rx.hstack(rx.chakra.pin_input(length=4, id="pin_input")),
@@ -63,8 +68,12 @@ def FormSubmit():
)
-def FormSubmitName():
- """App with a form using on_submit."""
+def FormSubmitName(form_component):
+ """App with a form using on_submit.
+
+ Args:
+ form_component: The str name of the form component to use.
+ """
import reflex as rx
class FormState(rx.State):
@@ -85,7 +94,7 @@ def FormSubmitName():
is_read_only=True,
id="token",
),
- rx.form.root(
+ eval(form_component)(
rx.vstack(
rx.chakra.input(name="name_input"),
rx.hstack(rx.chakra.pin_input(length=4, name="pin_input")),
@@ -128,7 +137,23 @@ def FormSubmitName():
@pytest.fixture(
- scope="session", params=[FormSubmit, FormSubmitName], ids=["id", "name"]
+ scope="session",
+ params=[
+ functools.partial(FormSubmit, form_component="rx.form.root"),
+ functools.partial(FormSubmitName, form_component="rx.form.root"),
+ functools.partial(FormSubmit, form_component="rx.el.form"),
+ functools.partial(FormSubmitName, form_component="rx.el.form"),
+ functools.partial(FormSubmit, form_component="rx.chakra.form"),
+ functools.partial(FormSubmitName, form_component="rx.chakra.form"),
+ ],
+ ids=[
+ "id-radix",
+ "name-radix",
+ "id-html",
+ "name-html",
+ "id-chakra",
+ "name-chakra",
+ ],
)
def form_submit(request, tmp_path_factory) -> Generator[AppHarness, None, None]:
"""Start FormSubmit app at tmp_path via AppHarness.
@@ -140,9 +165,11 @@ def form_submit(request, tmp_path_factory) -> Generator[AppHarness, None, None]:
Yields:
running AppHarness instance
"""
+ param_id = request._pyfuncitem.callspec.id.replace("-", "_")
with AppHarness.create(
root=tmp_path_factory.mktemp("form_submit"),
app_source=request.param, # type: ignore
+ app_name=request.param.func.__name__ + f"_{param_id}",
) as harness:
assert harness.app_instance is not None, "app is not running"
yield harness
diff --git a/reflex/components/chakra/forms/form.py b/reflex/components/chakra/forms/form.py
index 5d38f66b4..f9c9df856 100644
--- a/reflex/components/chakra/forms/form.py
+++ b/reflex/components/chakra/forms/form.py
@@ -1,39 +1,13 @@
"""Form components."""
from __future__ import annotations
-from hashlib import md5
-from typing import Any, Dict, Iterator
-
-from jinja2 import Environment
-
from reflex.components.chakra import ChakraComponent
from reflex.components.component import Component
-from reflex.components.tags import Tag
-from reflex.constants import Dirs, EventTriggers
-from reflex.event import EventChain
-from reflex.utils import imports
-from reflex.utils.format import format_event_chain, to_camel_case
-from reflex.vars import BaseVar, Var
-
-FORM_DATA = Var.create("form_data")
-HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string(
- """
- const handleSubmit_{{ handle_submit_unique_name }} = useCallback((ev) => {
- const $form = ev.target
- ev.preventDefault()
- const {{ form_data }} = {...Object.fromEntries(new FormData($form).entries()), ...{{ field_ref_mapping }}}
-
- {{ on_submit_event_chain }}
-
- if ({{ reset_on_submit }}) {
- $form.reset()
- }
- })
- """
-)
+from reflex.components.el.elements.forms import Form as HTMLForm
+from reflex.vars import Var
-class Form(ChakraComponent):
+class Form(ChakraComponent, HTMLForm):
"""A form component."""
tag = "Box"
@@ -41,112 +15,6 @@ class Form(ChakraComponent):
# What the form renders to.
as_: Var[str] = "form" # type: ignore
- # If true, the form will be cleared after submit.
- reset_on_submit: Var[bool] = False # type: ignore
-
- # The name used to make this form's submit handler function unique
- handle_submit_unique_name: Var[str]
-
- @classmethod
- def create(cls, *children, **props) -> Component:
- """Create a form component.
-
- Args:
- *children: The children of the form.
- **props: The properties of the form.
-
- Returns:
- The form component.
- """
- if "handle_submit_unique_name" in props:
- return super().create(*children, **props)
-
- # Render the form hooks and use the hash of the resulting code to create a unique name.
- props["handle_submit_unique_name"] = ""
- form = super().create(*children, **props)
- code_hash = md5(str(form.get_hooks()).encode("utf-8")).hexdigest()
- form.handle_submit_unique_name = code_hash
- return form
-
- def _get_imports(self) -> imports.ImportDict:
- return imports.merge_imports(
- super()._get_imports(),
- {
- "react": {imports.ImportVar(tag="useCallback")},
- f"/{Dirs.STATE_PATH}": {
- imports.ImportVar(tag="getRefValue"),
- imports.ImportVar(tag="getRefValues"),
- },
- },
- )
-
- def _get_hooks(self) -> str | None:
- if EventTriggers.ON_SUBMIT not in self.event_triggers:
- return
- return HANDLE_SUBMIT_JS_JINJA2.render(
- handle_submit_unique_name=self.handle_submit_unique_name,
- form_data=FORM_DATA,
- field_ref_mapping=str(Var.create_safe(self._get_form_refs())),
- on_submit_event_chain=format_event_chain(
- self.event_triggers[EventTriggers.ON_SUBMIT]
- ),
- reset_on_submit=self.reset_on_submit,
- )
-
- def _render(self) -> Tag:
- render_tag = (
- super()
- ._render()
- .remove_props(
- "reset_on_submit",
- "handle_submit_unique_name",
- to_camel_case(EventTriggers.ON_SUBMIT),
- )
- )
- if EventTriggers.ON_SUBMIT in self.event_triggers:
- render_tag.add_props(
- **{
- EventTriggers.ON_SUBMIT: BaseVar(
- _var_name=f"handleSubmit_{self.handle_submit_unique_name}",
- _var_type=EventChain,
- )
- }
- )
- return render_tag
-
- def _get_form_refs(self) -> Dict[str, Any]:
- # Send all the input refs to the handler.
- form_refs = {}
- for ref in self.get_refs():
- # when ref start with refs_ it's an array of refs, so we need different method
- # to collect data
- if ref.startswith("refs_"):
- ref_var = Var.create_safe(ref[:-3]).as_ref()
- form_refs[ref[5:-3]] = Var.create_safe(
- f"getRefValues({str(ref_var)})", _var_is_local=False
- )._replace(merge_var_data=ref_var._var_data)
- else:
- ref_var = Var.create_safe(ref).as_ref()
- form_refs[ref[4:]] = Var.create_safe(
- f"getRefValue({str(ref_var)})", _var_is_local=False
- )._replace(merge_var_data=ref_var._var_data)
- return form_refs
-
- def get_event_triggers(self) -> Dict[str, Any]:
- """Get the event triggers that pass the component's value to the handler.
-
- Returns:
- A dict mapping the event trigger to the var that is passed to the handler.
- """
- return {
- **super().get_event_triggers(),
- EventTriggers.ON_SUBMIT: lambda e0: [FORM_DATA],
- }
-
- def _get_vars(self) -> Iterator[Var]:
- yield from super()._get_vars()
- yield from self._get_form_refs().values()
-
class FormControl(ChakraComponent):
"""Provide context to form components."""
diff --git a/reflex/components/chakra/forms/form.pyi b/reflex/components/chakra/forms/form.pyi
index fd915a736..7db40d9e0 100644
--- a/reflex/components/chakra/forms/form.pyi
+++ b/reflex/components/chakra/forms/form.pyi
@@ -7,32 +7,85 @@ from typing import Any, Dict, Literal, Optional, Union, overload
from reflex.vars import Var, BaseVar, ComputedVar
from reflex.event import EventChain, EventHandler, EventSpec
from reflex.style import Style
-from hashlib import md5
-from typing import Any, Dict, Iterator
-from jinja2 import Environment
from reflex.components.chakra import ChakraComponent
from reflex.components.component import Component
-from reflex.components.tags import Tag
-from reflex.constants import Dirs, EventTriggers
-from reflex.event import EventChain
-from reflex.utils import imports
-from reflex.utils.format import format_event_chain, to_camel_case
-from reflex.vars import BaseVar, Var
+from reflex.components.el.elements.forms import Form as HTMLForm
+from reflex.vars import Var
-FORM_DATA = Var.create("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 "
-)
-
-class Form(ChakraComponent):
+class Form(ChakraComponent, HTMLForm):
@overload
@classmethod
def create( # type: ignore
cls,
*children,
as_: Optional[Union[Var[str], str]] = None,
+ accept: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ accept_charset: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ action: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ auto_complete: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ enc_type: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ method: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ name: Optional[Union[Var[Union[str, int, bool]], Union[str, int, bool]]] = None,
+ no_validate: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ target: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
reset_on_submit: Optional[Union[Var[bool], bool]] = None,
handle_submit_unique_name: Optional[Union[Var[str], str]] = None,
+ access_key: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ auto_capitalize: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ content_editable: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ context_menu: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ dir: Optional[Union[Var[Union[str, int, bool]], Union[str, int, bool]]] = None,
+ draggable: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ enter_key_hint: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ hidden: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ input_mode: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ item_prop: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ lang: Optional[Union[Var[Union[str, int, bool]], Union[str, int, bool]]] = None,
+ role: Optional[Union[Var[Union[str, int, bool]], Union[str, int, bool]]] = None,
+ slot: Optional[Union[Var[Union[str, int, bool]], Union[str, int, bool]]] = None,
+ spell_check: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ tab_index: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
+ title: Optional[
+ Union[Var[Union[str, int, bool]], Union[str, int, bool]]
+ ] = None,
style: Optional[Style] = None,
key: Optional[Any] = None,
id: Optional[Any] = None,
@@ -94,8 +147,33 @@ class Form(ChakraComponent):
Args:
*children: The children of the form.
as_: What the form renders to.
+ accept: MIME types the server accepts for file upload
+ accept_charset: Character encodings to be used for form submission
+ action: URL where the form's data should be submitted
+ auto_complete: Whether the form should have autocomplete enabled
+ enc_type: Encoding type for the form data when submitted
+ method: HTTP method to use for form submission
+ name: Name of the form
+ no_validate: Indicates that the form should not be validated on submit
+ target: Where to display the response after submitting the form
reset_on_submit: If true, the form will be cleared after submit.
- handle_submit_unique_name: The name used to make this form's submit handler function unique
+ handle_submit_unique_name: The name used to make this form's submit handler function unique.
+ access_key: Provides a hint for generating a keyboard shortcut for the current element.
+ auto_capitalize: Controls whether and how text input is automatically capitalized as it is entered/edited by the user.
+ content_editable: Indicates whether the element's content is editable.
+ context_menu: Defines the ID of a