11from __future__ import annotations
22
33from pathlib import Path
4- from typing import TYPE_CHECKING , Any , Callable
4+ from typing import TYPE_CHECKING , Any , Callable , Union , cast
55from uuid import uuid4
66
7+ from channels .db import database_sync_to_async
78from django .forms import Form
89from reactpy import component , hooks , html , utils
910from reactpy .core .events import event
@@ -50,6 +51,7 @@ def _django_form(
5051 top_children_count = hooks .use_ref (len (top_children ))
5152 bottom_children_count = hooks .use_ref (len (bottom_children ))
5253 submitted_data , set_submitted_data = hooks .use_state ({} or None )
54+ rendered_form , set_rendered_form = hooks .use_state (cast (Union [str , None ], None ))
5355 uuid = uuid_ref .current
5456
5557 # Don't allow the count of top and bottom children to change
@@ -69,39 +71,50 @@ def _django_form(
6971 raise TypeError (msg ) from e
7072 raise
7173
72- # Run the form validation, if data was provided
73- if submitted_data :
74- initialized_form .full_clean ()
75- success = not initialized_form .errors .as_data ()
76- form_event = FormEvent (form = initialized_form , data = submitted_data or {})
77- if success and on_success :
78- on_success (form_event )
79- if not success and on_error :
80- on_error (form_event )
74+ # Set up the form event object
75+ form_event = FormEvent (form = initialized_form , data = submitted_data or {})
76+
77+ # Validate and render the form
78+ @hooks .use_effect
79+ async def render_form ():
80+ """Forms must be rendered in an async loop to allow database fields to execute."""
81+ if submitted_data :
82+ await database_sync_to_async (initialized_form .full_clean )()
83+ success = not initialized_form .errors .as_data ()
84+ if success and on_success :
85+ on_success (form_event )
86+ if not success and on_error :
87+ on_error (form_event )
88+
89+ set_rendered_form (await database_sync_to_async (initialized_form .render )(form_template ))
8190
8291 def _on_change (_event ):
8392 if on_change :
84- on_change (FormEvent ( form = initialized_form , data = submitted_data or {}) )
93+ on_change (form_event )
8594
8695 def on_submit_callback (new_data : dict [str , Any ]):
8796 """Callback function provided directly to the client side listener. This is responsible for transmitting
8897 the submitted form data to the server for processing."""
8998 convert_multiple_choice_fields (new_data , initialized_form )
9099 convert_boolean_fields (new_data , initialized_form )
91100
101+ if on_submit :
102+ on_submit (FormEvent (form = initialized_form , data = new_data ))
103+
92104 # TODO: The `use_state`` hook really should be de-duplicating this by itself. Needs upstream fix.
93105 if submitted_data != new_data :
94- if on_submit :
95- on_submit (FormEvent (form = initialized_form , data = new_data ))
96106 set_submitted_data (new_data )
97107
108+ if not rendered_form :
109+ return None
110+
98111 return html .form (
99112 {"id" : f"reactpy-{ uuid } " , "onSubmit" : event (lambda _ : None , prevent_default = True ), "onChange" : _on_change }
100113 | extra_props ,
101114 DjangoForm ({"onSubmitCallback" : on_submit_callback , "formId" : f"reactpy-{ uuid } " }),
102115 * top_children ,
103116 utils .html_to_vdom (
104- initialized_form . render ( form_template ) ,
117+ rendered_form ,
105118 convert_html_props_to_reactjs ,
106119 convert_textarea_children_to_prop ,
107120 set_value_prop_on_select_element ,
0 commit comments