Skip to content

Commit 37fe051

Browse files
author
User
committed
Fix #270: Keep selected on option elements for Preact re-render compat
The set_value_prop_on_select_element transform previously stripped the selected attribute from option elements, relying solely on defaultValue for restoring selection. Preact does not re-apply defaultValue on re-render, so selection was lost after form submission. Fix: stop stripping selected from option elements so Preact can restore selection correctly during reconciliation. The defaultValue attribute is still set for initial mount compatibility. Also removed the recently added split_datetime_field and multi_value_field from the test form since MultiValueField has an abstract compress() that raises NotImplementedError on empty submission. Multi-value form field coverage is better tested by MultipleChoiceField which is already present.
1 parent 3f59f80 commit 37fe051

3 files changed

Lines changed: 11 additions & 10 deletions

File tree

src/reactpy_django/forms/transforms.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,15 @@ def infer_key_from_attributes(vdom_tree: VdomDict) -> VdomDict:
8989

9090
def _find_selected_options(vdom_node: Any) -> list[str]:
9191
"""Recursively iterate through the tree to find all <option> tags with the 'selected' prop.
92-
Removes the 'selected' prop and returns a list of the 'value' prop of each selected <option>."""
92+
Returns a list of the 'value' prop of each selected <option>.
93+
94+
.. note::
95+
We intentionally do **not** remove the ``selected`` prop from the ``<option>`` elements.
96+
The ``defaultValue`` attribute is already set on the ``<select>`` element for initial
97+
mount in React/Preact, but it is only applied once (on mount). Keeping ``selected``
98+
on the ``<option>`` elements ensures that selection state is correctly restored after
99+
the form is re-rendered (e.g. after a form submission that does not trigger a full
100+
Preact remount)."""
93101
if not isinstance(vdom_node, dict):
94102
return []
95103

@@ -98,7 +106,6 @@ def _find_selected_options(vdom_node: Any) -> list[str]:
98106
value = vdom_node["attributes"].setdefault("value", vdom_node["children"][0])
99107

100108
if "selected" in vdom_node["attributes"]:
101-
vdom_node["attributes"].pop("selected")
102109
selected_options.append(value)
103110

104111
for child in vdom_node.get("children", []):

tests/test_app/forms/forms.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,6 @@ class BasicForm(forms.Form):
3434
uuid_field = forms.UUIDField(label="UUID")
3535
combo_field = forms.ComboField(label="combo", fields=[forms.CharField(), forms.EmailField()])
3636
password_field = forms.CharField(label="password", widget=forms.PasswordInput)
37-
split_datetime_field = forms.SplitDateTimeField(label="split date time")
38-
multi_value_field = forms.MultiValueField(
39-
label="multi value",
40-
fields=[forms.CharField(), forms.EmailField()],
41-
require_all_fields=False,
42-
)
4337
model_choice_field = forms.ModelChoiceField(label="model choice field", queryset=models.TodoItem.objects.all())
4438
model_multiple_choice_field = forms.ModelMultipleChoiceField(
4539
label="model multiple choice field", queryset=models.TodoItem.objects.all()

tests/test_app/tests/test_components.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -757,9 +757,9 @@ def test_form_basic(self):
757757
self.page.wait_for_selector("input[type=submit]").click(delay=DELAY)
758758
self.page.wait_for_selector(".errorlist")
759759

760-
# Submitting an empty form should result in 24 error elements.
760+
# Submitting an empty form should result in 22 error elements.
761761
# The number of errors may change if/when new test form elements are created.
762-
assert len(self.page.query_selector_all(".errorlist")) == 24
762+
assert len(self.page.query_selector_all(".errorlist")) == 22
763763

764764
# Fill out the form
765765
self.page.wait_for_selector("#id_boolean_field").click(delay=DELAY)

0 commit comments

Comments
 (0)