11from __future__ import annotations
22
3+ import asyncio
34from pathlib import Path
45from typing import TYPE_CHECKING , Any , Callable , Union , cast
56from uuid import uuid4
1819 transform_value_prop_on_input_element ,
1920)
2021from reactpy_django .forms .utils import convert_boolean_fields , convert_multiple_choice_fields
21- from reactpy_django .types import FormEvent
22+ from reactpy_django .types import AsyncFormEvent , FormEventData , SyncFormEvent
2223
2324if TYPE_CHECKING :
2425 from collections .abc import Sequence
3132)
3233
3334
35+ # TODO: Create types for AsyncFormEvent and FormEvent
3436@component
3537def _django_form (
3638 form : type [Form | ModelForm ],
37- on_success : Callable [[ FormEvent ], None ] | None ,
38- on_error : Callable [[ FormEvent ], None ] | None ,
39- on_submit : Callable [[ FormEvent ], None ] | None ,
40- on_change : Callable [[ FormEvent ], None ] | None ,
39+ on_success : AsyncFormEvent | SyncFormEvent | None ,
40+ on_error : AsyncFormEvent | SyncFormEvent | None ,
41+ on_receive_data : AsyncFormEvent | SyncFormEvent | None ,
42+ on_change : AsyncFormEvent | SyncFormEvent | None ,
4143 auto_save : bool ,
4244 extra_props : dict ,
4345 extra_transforms : Sequence [Callable [[VdomDict ], Any ]],
@@ -64,13 +66,12 @@ def _django_form(
6466 "Do NOT initialize your form by calling it (ex. `MyForm()`)."
6567 )
6668 raise TypeError (msg )
67- if "id" in extra_props :
68- msg = "The `extra_props` argument cannot contain an `id` key."
69- raise ValueError (msg )
7069
7170 # Try to initialize the form with the provided data
7271 initialized_form = form (data = submitted_data )
73- form_event = FormEvent (form = initialized_form , data = submitted_data or {}, set_data = set_submitted_data )
72+ form_event = FormEventData (
73+ form = initialized_form , submitted_data = submitted_data or {}, set_submitted_data = set_submitted_data
74+ )
7475
7576 # Validate and render the form
7677 @hooks .use_effect
@@ -80,9 +81,15 @@ async def render_form():
8081 await database_sync_to_async (initialized_form .full_clean )()
8182 success = not initialized_form .errors .as_data ()
8283 if success and on_success :
83- on_success (form_event )
84+ if asyncio .iscoroutinefunction (on_success ):
85+ await on_success (form_event )
86+ else :
87+ on_success (form_event )
8488 if not success and on_error :
85- on_error (form_event )
89+ if asyncio .iscoroutinefunction (on_error ):
90+ await on_error (form_event )
91+ else :
92+ on_error (form_event )
8693 if success and auto_save and isinstance (initialized_form , ModelForm ):
8794 await database_sync_to_async (initialized_form .save )()
8895 set_submitted_data (None )
@@ -93,28 +100,43 @@ async def render_form():
93100 if new_form != rendered_form :
94101 set_rendered_form (new_form )
95102
96- def on_submit_callback (new_data : dict [str , Any ]):
103+ async def on_submit_callback (new_data : dict [str , Any ]):
97104 """Callback function provided directly to the client side listener. This is responsible for transmitting
98105 the submitted form data to the server for processing."""
99106 convert_multiple_choice_fields (new_data , initialized_form )
100107 convert_boolean_fields (new_data , initialized_form )
101108
102- if on_submit :
103- on_submit (FormEvent (form = initialized_form , data = new_data , set_data = set_submitted_data ))
109+ if on_receive_data :
110+ new_form_event = FormEventData (
111+ form = initialized_form , submitted_data = new_data , set_submitted_data = set_submitted_data
112+ )
113+ if asyncio .iscoroutinefunction (on_receive_data ):
114+ await on_receive_data (new_form_event )
115+ else :
116+ on_receive_data (new_form_event )
104117
105118 if submitted_data != new_data :
106119 set_submitted_data (new_data )
107120
121+ async def _on_change (_event ):
122+ """Event that exist solely to allow the user to detect form changes."""
123+ if on_change :
124+ if asyncio .iscoroutinefunction (on_change ):
125+ await on_change (form_event )
126+ else :
127+ on_change (form_event )
128+
108129 if not rendered_form :
109130 return None
110131
111132 return html .form (
112- {
133+ extra_props
134+ | {
113135 "id" : f"reactpy-{ uuid } " ,
136+ # Intercept the form submission to prevent the browser from navigating
114137 "onSubmit" : event (lambda _ : None , prevent_default = True ),
115- "onChange" : on_change (form_event ) if on_change else lambda _ : None ,
116- }
117- | extra_props ,
138+ "onChange" : _on_change ,
139+ },
118140 DjangoForm ({"onSubmitCallback" : on_submit_callback , "formId" : f"reactpy-{ uuid } " }),
119141 * top_children ,
120142 utils .html_to_vdom (
0 commit comments