@@ -34,6 +34,7 @@ import {
3434import {
3535 Locale ,
3636 PydanticFormContextProps ,
37+ PydanticFormFieldType ,
3738 PydanticFormInitialContextProps ,
3839 PydanticFormSchemaRawJson ,
3940 PydanticFormValidationErrorDetails ,
@@ -61,8 +62,6 @@ function PydanticFormContextProvider({
6162 labelProvider,
6263 customDataProvider,
6364 customDataProviderCacheKey,
64- formStructureMutator,
65- fieldDetailProvider,
6665 resetButtonAlternative,
6766 allowUntouchedSubmit,
6867 skipSuccessNotice,
@@ -78,15 +77,15 @@ function PydanticFormContextProvider({
7877
7978 const formRef = useRef < string > ( formKey ) ;
8079
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+ ) ;
9089
9190 const goToPreviousStep = ( formInput : object ) => {
9291 setFormInputData ( ( prevState ) => {
@@ -95,19 +94,6 @@ function PydanticFormContextProvider({
9594 } ) ;
9695 } ;
9796
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-
11197 const [ errorDetails , setErrorDetails ] =
11298 useState < PydanticFormValidationErrorDetails > ( ) ;
11399 const [ isFullFilled , setIsFullFilled ] = useState ( false ) ;
@@ -129,17 +115,21 @@ function PydanticFormContextProvider({
129115 error,
130116 } = useApiProvider ( formKey , formInputData , apiProvider , metaData ) ;
131117
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 ) ;
133128 const [ hasNext , setHasNext ] = useState < boolean > ( false ) ;
134129
135130 // extract the JSON schema to a more usable custom schema
136131 const { pydanticFormSchema, isLoading : isParsingSchema } =
137- usePydanticFormParser (
138- rawSchema ,
139- formLabels ?. labels ,
140- fieldDetailProvider ,
141- formStructureMutator ,
142- ) ;
132+ usePydanticFormParser ( rawSchema , formLabels ?. labels ) ;
143133
144134 // build validation rules based on custom schema
145135 const zodSchema = useGetZodValidator (
@@ -169,17 +159,39 @@ function PydanticFormContextProvider({
169159 mode : 'all' ,
170160 defaultValues : initialData ,
171161 values : initialData ,
172- shouldUnregister : true ,
173162 } ) ;
174163
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+
175186 const submitFormFn = useCallback ( ( ) => {
176187 setIsSending ( true ) ;
177- const rhfValues = rhf . getValues ( ) ;
188+ const rhfValues = _ . cloneDeep ( rhf . getValues ( ) ) ;
189+ awaitReset ( ) ;
178190 // Note. If we don't use cloneDeep here we are adding a reference to the rhfValues
179191 // that changes on every change in the form and triggering effects before we want to.
180- addFormInputData ( _ . cloneDeep ( rhfValues ) , ! ! errorDetails ) ;
192+ addFormInputData ( rhfValues , ! ! errorDetails ) ;
181193 window . scrollTo ( 0 , 0 ) ;
182- } , [ rhf , errorDetails , addFormInputData ] ) ;
194+ } , [ rhf , errorDetails , addFormInputData , awaitReset , setIsSending ] ) ;
183195
184196 const onClientSideError = useCallback (
185197 ( data ?: FieldValues ) => {
@@ -198,10 +210,10 @@ function PydanticFormContextProvider({
198210 ( e : React . MouseEvent < HTMLButtonElement , MouseEvent > ) => {
199211 e . preventDefault ( ) ;
200212 setErrorDetails ( undefined ) ;
201- rhf . reset ( ) ;
213+ awaitReset ( ) ;
202214 rhf . trigger ( ) ;
203215 } ,
204- [ rhf ] ,
216+ [ awaitReset , rhf ] ,
205217 ) ;
206218
207219 const resetErrorDetails = useCallback ( ( ) => {
@@ -217,9 +229,9 @@ function PydanticFormContextProvider({
217229 const clearForm = useCallback ( ( ) => {
218230 setFormInputData ( [ ] ) ;
219231 setIsFullFilled ( false ) ;
220- setRawSchema ( undefined ) ;
232+ setRawSchema ( emptyRawSchema ) ;
221233 setHasNext ( false ) ;
222- } , [ ] ) ;
234+ } , [ emptyRawSchema ] ) ;
223235
224236 const PydanticFormContextState = {
225237 // to prevent an issue where the sending state hangs
@@ -253,40 +265,23 @@ function PydanticFormContextProvider({
253265 initialData,
254266 } ;
255267
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
267271 useEffect ( ( ) => {
268- if ( ! isFullFilled ) {
272+ if ( ! apiResponse ) {
269273 return ;
270274 }
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 ;
281282 }
282283
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 ( ) ;
290285 if ( apiResponse ?. success ) {
291286 setIsFullFilled ( true ) ;
292287 return ;
@@ -301,35 +296,52 @@ function PydanticFormContextProvider({
301296 setErrorDetails ( undefined ) ;
302297 }
303298
304- // when we receive errors, we append to the scheme
305- if ( apiResponse ?. validation_errors ) {
306- setErrorDetails ( getErrorDetailsFromResponse ( apiResponse ) ) ;
307- }
308-
309299 setIsSending ( false ) ;
310300 // eslint-disable-next-line react-hooks/exhaustive-deps
311301 } , [ apiResponse ] ) ; // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
312302 // a useeffect for filling data whenever formdefinition or labels update
313303
304+ // When a formKey changes we reset the form input data
314305 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 ] ) ;
317314
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
320329 }
321- } ) ;
322- } , [ formInputData , formInputHistory , rhf ] ) ;
330+ }
323331
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
326338 useEffect ( ( ) => {
327339 if ( ! error ) {
328340 return ;
329341 }
330342
331343 setErrorDetails ( {
332- detail : 'Er is iets misgegaan bij het verzenden. ' ,
344+ detail : 'Something unexpected went wrong ' ,
333345 source : [ ] ,
334346 mapped : { } ,
335347 } ) ;
@@ -350,6 +362,15 @@ function PydanticFormContextProvider({
350362 z . config ( getLocale ( ) ) ;
351363 } , [ locale ] ) ;
352364
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+
353374 return (
354375 < PydanticFormContext . Provider value = { PydanticFormContextState } >
355376 { children ( PydanticFormContextState ) }
0 commit comments