@@ -34,6 +34,7 @@ import {
34
34
import {
35
35
Locale ,
36
36
PydanticFormContextProps ,
37
+ PydanticFormFieldType ,
37
38
PydanticFormInitialContextProps ,
38
39
PydanticFormSchemaRawJson ,
39
40
PydanticFormValidationErrorDetails ,
@@ -61,8 +62,6 @@ function PydanticFormContextProvider({
61
62
labelProvider,
62
63
customDataProvider,
63
64
customDataProviderCacheKey,
64
- formStructureMutator,
65
- fieldDetailProvider,
66
65
resetButtonAlternative,
67
66
allowUntouchedSubmit,
68
67
skipSuccessNotice,
@@ -78,15 +77,15 @@ function PydanticFormContextProvider({
78
77
79
78
const formRef = useRef < string > ( formKey ) ;
80
79
81
- const updateHistory = async (
82
- formInput : object ,
83
- previousSteps : object [ ] ,
84
- ) => {
85
- const hashOfPreviousSteps = await getHashForArray ( previousSteps ) ;
86
- setFormInputHistory ( ( prevState ) =>
87
- prevState . set ( hashOfPreviousSteps , formInput ) ,
88
- ) ;
89
- } ;
80
+ const updateHistory = useCallback (
81
+ async ( formInput : object , previousSteps : object [ ] ) => {
82
+ const hashOfPreviousSteps = await getHashForArray ( previousSteps ) ;
83
+ setFormInputHistory ( ( prevState ) =>
84
+ prevState . set ( hashOfPreviousSteps , formInput ) ,
85
+ ) ;
86
+ } ,
87
+ [ ] ,
88
+ ) ;
90
89
91
90
const goToPreviousStep = ( formInput : object ) => {
92
91
setFormInputData ( ( prevState ) => {
@@ -95,19 +94,6 @@ function PydanticFormContextProvider({
95
94
} ) ;
96
95
} ;
97
96
98
- const addFormInputData = useCallback (
99
- ( formInput : object , replaceInsteadOfAdd = false ) => {
100
- setFormInputData ( ( currentInputs ) => {
101
- const data = replaceInsteadOfAdd
102
- ? currentInputs . slice ( 0 , - 1 )
103
- : currentInputs ;
104
- updateHistory ( formInput , data ) ;
105
- return [ ...data , formInput ] ;
106
- } ) ;
107
- } ,
108
- [ ] ,
109
- ) ;
110
-
111
97
const [ errorDetails , setErrorDetails ] =
112
98
useState < PydanticFormValidationErrorDetails > ( ) ;
113
99
const [ isFullFilled , setIsFullFilled ] = useState ( false ) ;
@@ -129,17 +115,21 @@ function PydanticFormContextProvider({
129
115
error,
130
116
} = useApiProvider ( formKey , formInputData , apiProvider , metaData ) ;
131
117
132
- const [ rawSchema , setRawSchema ] = useState < PydanticFormSchemaRawJson > ( ) ;
118
+ const emptyRawSchema : PydanticFormSchemaRawJson = useMemo (
119
+ ( ) => ( {
120
+ type : PydanticFormFieldType . OBJECT ,
121
+ properties : { } ,
122
+ } ) ,
123
+ [ ] ,
124
+ ) ;
125
+
126
+ const [ rawSchema , setRawSchema ] =
127
+ useState < PydanticFormSchemaRawJson > ( emptyRawSchema ) ;
133
128
const [ hasNext , setHasNext ] = useState < boolean > ( false ) ;
134
129
135
130
// extract the JSON schema to a more usable custom schema
136
131
const { pydanticFormSchema, isLoading : isParsingSchema } =
137
- usePydanticFormParser (
138
- rawSchema ,
139
- formLabels ?. labels ,
140
- fieldDetailProvider ,
141
- formStructureMutator ,
142
- ) ;
132
+ usePydanticFormParser ( rawSchema , formLabels ?. labels ) ;
143
133
144
134
// build validation rules based on custom schema
145
135
const zodSchema = useGetZodValidator (
@@ -169,17 +159,39 @@ function PydanticFormContextProvider({
169
159
mode : 'all' ,
170
160
defaultValues : initialData ,
171
161
values : initialData ,
172
- shouldUnregister : true ,
173
162
} ) ;
174
163
164
+ const awaitReset = useCallback (
165
+ async ( payLoad : FieldValues = { } ) => {
166
+ rhf . reset ( payLoad ) ;
167
+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ; // wait one tick
168
+ } ,
169
+ [ rhf ] ,
170
+ ) ;
171
+
172
+ const addFormInputData = useCallback (
173
+ ( formInput : object , replaceInsteadOfAdd = false ) => {
174
+ setFormInputData ( ( currentInputs ) => {
175
+ const data = replaceInsteadOfAdd
176
+ ? currentInputs . slice ( 0 , - 1 )
177
+ : currentInputs ;
178
+ updateHistory ( formInput , data ) ;
179
+ return [ ...data , { ...formInput } ] ;
180
+ } ) ;
181
+ awaitReset ( ) ;
182
+ } ,
183
+ [ awaitReset , setFormInputData , updateHistory ] ,
184
+ ) ;
185
+
175
186
const submitFormFn = useCallback ( ( ) => {
176
187
setIsSending ( true ) ;
177
- const rhfValues = rhf . getValues ( ) ;
188
+ const rhfValues = _ . cloneDeep ( rhf . getValues ( ) ) ;
189
+ awaitReset ( ) ;
178
190
// Note. If we don't use cloneDeep here we are adding a reference to the rhfValues
179
191
// that changes on every change in the form and triggering effects before we want to.
180
- addFormInputData ( _ . cloneDeep ( rhfValues ) , ! ! errorDetails ) ;
192
+ addFormInputData ( rhfValues , ! ! errorDetails ) ;
181
193
window . scrollTo ( 0 , 0 ) ;
182
- } , [ rhf , errorDetails , addFormInputData ] ) ;
194
+ } , [ rhf , errorDetails , addFormInputData , awaitReset , setIsSending ] ) ;
183
195
184
196
const onClientSideError = useCallback (
185
197
( data ?: FieldValues ) => {
@@ -198,10 +210,10 @@ function PydanticFormContextProvider({
198
210
( e : React . MouseEvent < HTMLButtonElement , MouseEvent > ) => {
199
211
e . preventDefault ( ) ;
200
212
setErrorDetails ( undefined ) ;
201
- rhf . reset ( ) ;
213
+ awaitReset ( ) ;
202
214
rhf . trigger ( ) ;
203
215
} ,
204
- [ rhf ] ,
216
+ [ awaitReset , rhf ] ,
205
217
) ;
206
218
207
219
const resetErrorDetails = useCallback ( ( ) => {
@@ -217,9 +229,9 @@ function PydanticFormContextProvider({
217
229
const clearForm = useCallback ( ( ) => {
218
230
setFormInputData ( [ ] ) ;
219
231
setIsFullFilled ( false ) ;
220
- setRawSchema ( undefined ) ;
232
+ setRawSchema ( emptyRawSchema ) ;
221
233
setHasNext ( false ) ;
222
- } , [ ] ) ;
234
+ } , [ emptyRawSchema ] ) ;
223
235
224
236
const PydanticFormContextState = {
225
237
// to prevent an issue where the sending state hangs
@@ -253,40 +265,23 @@ function PydanticFormContextProvider({
253
265
initialData,
254
266
} ;
255
267
256
- /** UseEffects */
257
- useEffect ( ( ) => {
258
- if ( formKey !== formRef . current ) {
259
- // When the formKey changes we need to reset the form input data
260
- setFormInputData ( [ ] ) ;
261
- setFormInputHistory ( new Map < string , object > ( ) ) ;
262
- formRef . current = formKey ;
263
- }
264
- } , [ formKey ] ) ;
265
-
266
- // handle successfull submits
268
+ // a useeffect for whenever the error response updates
269
+ // sometimes we need to update the form,
270
+ // some we need to update the errors
267
271
useEffect ( ( ) => {
268
- if ( ! isFullFilled ) {
272
+ if ( ! apiResponse ) {
269
273
return ;
270
274
}
271
-
272
- if ( onSuccess ) {
273
- const values = rhf . getValues ( ) ;
274
- if ( skipSuccessNotice ) {
275
- onSuccess ( values , apiResponse || { } ) ;
276
- } else {
277
- setTimeout ( ( ) => {
278
- onSuccess ?.( values , apiResponse || { } ) ;
279
- } , 1500 ) ; // Delay to allow notice to show first
280
- }
275
+ // when we receive errors, we append to the scheme
276
+ if ( apiResponse ?. validation_errors ) {
277
+ // Restore the data we got the error with
278
+ const errorPayload = [ ...formInputData ] . pop ( ) ;
279
+ awaitReset ( errorPayload ) ;
280
+ setErrorDetails ( getErrorDetailsFromResponse ( apiResponse ) ) ;
281
+ return ;
281
282
}
282
283
283
- setFormInputHistory ( new Map < string , object > ( ) ) ;
284
- } , [ apiResponse , isFullFilled , onSuccess , rhf , skipSuccessNotice ] ) ;
285
-
286
- // a useeffect for whenever the error response updates
287
- // sometimes we need to update the form,
288
- // some we need to update the errors
289
- useEffect ( ( ) => {
284
+ awaitReset ( ) ;
290
285
if ( apiResponse ?. success ) {
291
286
setIsFullFilled ( true ) ;
292
287
return ;
@@ -301,35 +296,52 @@ function PydanticFormContextProvider({
301
296
setErrorDetails ( undefined ) ;
302
297
}
303
298
304
- // when we receive errors, we append to the scheme
305
- if ( apiResponse ?. validation_errors ) {
306
- setErrorDetails ( getErrorDetailsFromResponse ( apiResponse ) ) ;
307
- }
308
-
309
299
setIsSending ( false ) ;
310
300
// eslint-disable-next-line react-hooks/exhaustive-deps
311
301
} , [ apiResponse ] ) ; // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
312
302
// a useeffect for filling data whenever formdefinition or labels update
313
303
304
+ // When a formKey changes we reset the form input data
314
305
useEffect ( ( ) => {
315
- getHashForArray ( formInputData ) . then ( ( hash ) => {
316
- const currentStepFromHistory = formInputHistory . get ( hash ) ;
306
+ if ( formKey !== formRef . current ) {
307
+ // When the formKey changes we need to reset the form input data
308
+ setFormInputData ( [ ] ) ;
309
+ setFormInputHistory ( new Map < string , object > ( ) ) ;
310
+ awaitReset ( ) ;
311
+ formRef . current = formKey ;
312
+ }
313
+ } , [ awaitReset , formKey ] ) ;
317
314
318
- if ( currentStepFromHistory ) {
319
- rhf . reset ( currentStepFromHistory ) ;
315
+ // handle successfull submits
316
+ useEffect ( ( ) => {
317
+ if ( ! isFullFilled ) {
318
+ return ;
319
+ }
320
+
321
+ if ( onSuccess ) {
322
+ const values = rhf . getValues ( ) ;
323
+ if ( skipSuccessNotice ) {
324
+ onSuccess ( values , apiResponse || { } ) ;
325
+ } else {
326
+ setTimeout ( ( ) => {
327
+ onSuccess ?.( values , apiResponse || { } ) ;
328
+ } , 1500 ) ; // Delay to allow notice to show first
320
329
}
321
- } ) ;
322
- } , [ formInputData , formInputHistory , rhf ] ) ;
330
+ }
323
331
324
- // this is to show an error whenever there is an unexpected error from the backend
325
- // for instance a 500
332
+ setFormInputHistory ( new Map < string , object > ( ) ) ;
333
+ // eslint-disable-next-line react-hooks/exhaustive-deps
334
+ } , [ apiResponse , isFullFilled ] ) ; // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
335
+
336
+ // this handles errors throws by the useApiProvider call
337
+ // for instance unexpected 500 errors
326
338
useEffect ( ( ) => {
327
339
if ( ! error ) {
328
340
return ;
329
341
}
330
342
331
343
setErrorDetails ( {
332
- detail : 'Er is iets misgegaan bij het verzenden. ' ,
344
+ detail : 'Something unexpected went wrong ' ,
333
345
source : [ ] ,
334
346
mapped : { } ,
335
347
} ) ;
@@ -350,6 +362,15 @@ function PydanticFormContextProvider({
350
362
z . config ( getLocale ( ) ) ;
351
363
} , [ locale ] ) ;
352
364
365
+ useEffect ( ( ) => {
366
+ getHashForArray ( formInputData ) . then ( ( hash ) => {
367
+ const currentStepFromHistory = formInputHistory . get ( hash ) ;
368
+ if ( currentStepFromHistory ) {
369
+ awaitReset ( currentStepFromHistory ) ;
370
+ }
371
+ } ) ;
372
+ } , [ awaitReset , formInputData , formInputHistory , rhf ] ) ;
373
+
353
374
return (
354
375
< PydanticFormContext . Provider value = { PydanticFormContextState } >
355
376
{ children ( PydanticFormContextState ) }
0 commit comments