Skip to content

Commit

Permalink
Fix bug where input elements were dismounted prematurely
Browse files Browse the repository at this point in the history
  • Loading branch information
Archmonger committed Dec 11, 2024
1 parent 998041a commit 78d5f38
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 12 deletions.
16 changes: 8 additions & 8 deletions src/reactpy_django/forms/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from reactpy_django.forms.transforms import (
convert_html_props_to_reactjs,
convert_textarea_children_to_prop,
infer_key_from_attributes,
intercept_anchor_links,
set_value_prop_on_select_element,
transform_value_prop_on_input_element,
Expand Down Expand Up @@ -56,7 +57,7 @@ def _django_form(
rendered_form, set_rendered_form = hooks.use_state(cast(Union[str, None], None))
uuid = uuid_ref.current

# Check the provided arguments
# Validate the provided arguments
if len(top_children) != top_children_count.current or len(bottom_children) != bottom_children_count.current:
msg = "Dynamically changing the number of top or bottom children is not allowed."
raise ValueError(msg)
Expand All @@ -67,14 +68,14 @@ def _django_form(
)
raise TypeError(msg)

# Try to initialize the form with the provided data
# Initialize the form with the provided data
initialized_form = form(data=submitted_data)
form_event = FormEventData(
form=initialized_form, submitted_data=submitted_data or {}, set_submitted_data=set_submitted_data
)

# Validate and render the form
@hooks.use_effect
@hooks.use_effect(dependencies=[str(submitted_data)])
async def render_form():
"""Forms must be rendered in an async loop to allow database fields to execute."""
if submitted_data:
Expand All @@ -85,14 +86,12 @@ async def render_form():
if not success and on_error:
await ensure_async(on_error, thread_sensitive=thread_sensitive)(form_event)
if success and auto_save and isinstance(initialized_form, ModelForm):
await database_sync_to_async(initialized_form.save)()
await ensure_async(initialized_form.save)()
set_submitted_data(None)

new_form = await database_sync_to_async(initialized_form.render)(
form_template or config.REACTPY_DEFAULT_FORM_TEMPLATE
set_rendered_form(
await ensure_async(initialized_form.render)(form_template or config.REACTPY_DEFAULT_FORM_TEMPLATE)
)
if new_form != rendered_form:
set_rendered_form(new_form)

async def on_submit_callback(new_data: dict[str, Any]):
"""Callback function provided directly to the client side listener. This is responsible for transmitting
Expand Down Expand Up @@ -134,6 +133,7 @@ async def _on_change(_event):
set_value_prop_on_select_element,
transform_value_prop_on_input_element,
intercept_anchor_links,
infer_key_from_attributes,
*extra_transforms,
strict=False,
),
Expand Down
17 changes: 17 additions & 0 deletions src/reactpy_django/forms/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,23 @@ def intercept_anchor_links(vdom_tree: VdomDict) -> VdomDict:
return vdom_tree


def infer_key_from_attributes(vdom_tree: VdomDict) -> VdomDict:
"""Infer the node's 'key' by looking at any attributes that should be unique."""
attributes = vdom_tree.get("attributes", {})

# Infer 'key' from 'id'
_id = attributes.get("id")

# Fallback: Infer 'key' from 'name'
if not _id and vdom_tree["tagName"] in {"input", "select", "textarea"}:
_id = attributes.get("name")

if _id:
vdom_tree["key"] = _id

return vdom_tree


def _find_selected_options(vdom_node: Any) -> list[str]:
"""Recursively iterate through the tree to find all <option> tags with the 'selected' prop.
Removes the 'selected' prop and returns a list of the 'value' prop of each selected <option>."""
Expand Down
4 changes: 0 additions & 4 deletions tests/test_app/tests/test_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,10 +805,6 @@ def test_model_form(self):
# Make sure no errors remain
assert len(self.page.query_selector_all(".errorlist")) == 0

# Make sure text field is empty
expect(self.page.locator("#id_text")).to_be_empty()
assert self.page.locator("#id_text").get_attribute("value") == ""

# Check if `auto_save` created the TodoItem's database entry
try:
from test_app.models import TodoItem
Expand Down

0 comments on commit 78d5f38

Please sign in to comment.