@@ -14,7 +14,6 @@ import React, {
1414 useState ,
1515} from 'react' ;
1616import { FieldValues , useForm } from 'react-hook-form' ;
17- import { Subscription } from 'react-hook-form/dist/utils/createSubject' ;
1817
1918import _ from 'lodash' ;
2019import { z } from 'zod/v4' ;
@@ -54,7 +53,6 @@ function PydanticFormContextProvider({
5453 successNotice,
5554 onSuccess,
5655 onCancel,
57- onChange,
5856 children,
5957 config,
6058} : PydanticFormInitialContextProps ) {
@@ -65,7 +63,6 @@ function PydanticFormContextProvider({
6563 customDataProviderCacheKey,
6664 formStructureMutator,
6765 fieldDetailProvider,
68- onFieldChangeHandler,
6966 resetButtonAlternative,
7067 allowUntouchedSubmit,
7168 skipSuccessNotice,
@@ -81,21 +78,6 @@ function PydanticFormContextProvider({
8178
8279 const formRef = useRef < string > ( formKey ) ;
8380
84- useEffect ( ( ) => {
85- const getLocale = ( ) => {
86- switch ( locale ) {
87- case Locale . enGB :
88- return z . locales . en ( ) ;
89- case Locale . nlNL :
90- return z . locales . nl ( ) ;
91- default :
92- return z . locales . en ( ) ;
93- }
94- } ;
95-
96- z . config ( getLocale ( ) ) ;
97- } , [ locale ] ) ;
98-
9981 const updateHistory = async (
10082 formInput : object ,
10183 previousSteps : object [ ] ,
@@ -131,10 +113,7 @@ function PydanticFormContextProvider({
131113 const [ isFullFilled , setIsFullFilled ] = useState ( false ) ;
132114 const [ isSending , setIsSending ] = useState ( false ) ;
133115
134- // TODO: Fix leave confirmation functionality
135- const [ , setSaveToLeavePageInCurrentState ] = useState ( false ) ;
136-
137- // fetch the labels of the form, but can also include the current form values
116+ // fetch the labels of the form, can also contain default values
138117 const { data : formLabels , isLoading : isLoadingFormLabels } =
139118 useLabelProvider ( labelProvider , formKey , formIdKey ) ;
140119
@@ -144,20 +123,16 @@ function PydanticFormContextProvider({
144123 customDataProvider ,
145124 ) ;
146125
147- // fetch the form definition using SWR hook
148126 const {
149127 data : apiResponse ,
150128 isLoading : isLoadingSchema ,
151129 error,
152130 } = useApiProvider ( formKey , formInputData , apiProvider , metaData ) ;
153131
154- // we cache the form scheme so when there is an error, we still have the form
155- // the form is not in the error response
156132 const [ rawSchema , setRawSchema ] = useState < PydanticFormSchemaRawJson > ( ) ;
157133 const [ hasNext , setHasNext ] = useState < boolean > ( false ) ;
158134
159135 // extract the JSON schema to a more usable custom schema
160-
161136 const { pydanticFormSchema, isLoading : isParsingSchema } =
162137 usePydanticFormParser (
163138 rawSchema ,
@@ -166,12 +141,9 @@ function PydanticFormContextProvider({
166141 formStructureMutator ,
167142 ) ;
168143
169- const rhfRef = useRef < ReturnType < typeof useForm > > ( ) ;
170-
171144 // build validation rules based on custom schema
172145 const zodSchema = useGetZodValidator (
173146 pydanticFormSchema ,
174- rhfRef . current ,
175147 componentMatcherExtender ,
176148 ) ;
177149
@@ -197,42 +169,99 @@ function PydanticFormContextProvider({
197169 mode : 'all' ,
198170 defaultValues : initialData ,
199171 values : initialData ,
172+ shouldUnregister : true ,
200173 } ) ;
201174
175+ const submitFormFn = useCallback ( ( ) => {
176+ setIsSending ( true ) ;
177+ const rhfValues = rhf . getValues ( ) ;
178+ // Note. If we don't use cloneDeep here we are adding a reference to the rhfValues
179+ // that changes on every change in the form and triggering effects before we want to.
180+ addFormInputData ( _ . cloneDeep ( rhfValues ) , ! ! errorDetails ) ;
181+ window . scrollTo ( 0 , 0 ) ;
182+ } , [ rhf , errorDetails , addFormInputData ] ) ;
183+
184+ const onClientSideError = useCallback (
185+ ( data ?: FieldValues ) => {
186+ // TODO implement save with errors toggle
187+ if ( data ) {
188+ rhf . clearErrors ( ) ;
189+ submitFormFn ( ) ;
190+ }
191+ } ,
192+ [ rhf , submitFormFn ] ,
193+ ) ;
194+
195+ const submitForm = rhf . handleSubmit ( submitFormFn , onClientSideError ) ;
196+
197+ const resetForm = useCallback (
198+ ( e : React . MouseEvent < HTMLButtonElement , MouseEvent > ) => {
199+ e . preventDefault ( ) ;
200+ setErrorDetails ( undefined ) ;
201+ rhf . reset ( ) ;
202+ rhf . trigger ( ) ;
203+ } ,
204+ [ rhf ] ,
205+ ) ;
206+
207+ const resetErrorDetails = useCallback ( ( ) => {
208+ setErrorDetails ( undefined ) ;
209+ } , [ ] ) ;
210+
211+ const isLoading =
212+ isLoadingFormLabels ||
213+ isLoadingSchema ||
214+ isParsingSchema ||
215+ ( customDataProvider ? isLoadingCustomData : false ) ;
216+
217+ const clearForm = useCallback ( ( ) => {
218+ setFormInputData ( [ ] ) ;
219+ setIsFullFilled ( false ) ;
220+ setRawSchema ( undefined ) ;
221+ setHasNext ( false ) ;
222+ } , [ ] ) ;
223+
224+ const PydanticFormContextState = {
225+ // to prevent an issue where the sending state hangs
226+ // we check both the SWR hook state as our manual state
227+ isSending : isSending && isLoadingSchema ,
228+ isLoading,
229+ rhf,
230+ pydanticFormSchema,
231+ loadingComponent,
232+ onPrevious : ( ) => goToPreviousStep ( rhf ?. getValues ( ) ) ,
233+ onCancel,
234+ title,
235+ sendLabel,
236+ isFullFilled,
237+ customDataProvider,
238+ errorDetails,
239+ resetErrorDetails,
240+ successNotice,
241+ submitForm,
242+ resetForm,
243+ cancelButton,
244+ skipSuccessNotice,
245+ allowUntouchedSubmit,
246+ resetButtonAlternative,
247+ config,
248+ formKey,
249+ formIdKey,
250+ clearForm,
251+ formInputData,
252+ hasNext,
253+ initialData,
254+ } ;
255+
256+ /** UseEffects */
202257 useEffect ( ( ) => {
203258 if ( formKey !== formRef . current ) {
204259 // When the formKey changes we need to reset the form input data
205260 setFormInputData ( [ ] ) ;
206261 setFormInputHistory ( new Map < string , object > ( ) ) ;
207- rhf ?. reset ( { } ) ;
208262 formRef . current = formKey ;
209263 }
210- } , [ formKey , rhf ] ) ;
211-
212- rhfRef . current = rhf ;
213-
214- // Adds watch subscripton on form values
215- useEffect ( ( ) => {
216- const sub = rhf . watch ( ( values ) => {
217- setSaveToLeavePageInCurrentState ( false ) ;
218- onChange ?.( values ) ;
219- } ) ;
220-
221- return ( ) => sub . unsubscribe ( ) ;
222- } , [ rhf , onChange ] ) ;
223-
224- /* TODO: Reimplement
225- // prevent user from navigating away when there are unsaved changes
226- const hasUnsavedData =
227- !saveToLeavePageInCurrentState &&
228- !isFullFilled &&
229- rhf.formState.isDirty;
230-
231- useLeavePageConfirm(
232- hasUnsavedData,
233- 'Er zijn aanpassingen in het formulier. \nWeet je zeker dat je de pagina wilt verlaten?',
234- );
235- */
264+ } , [ formKey ] ) ;
236265
237266 // handle successfull submits
238267 useEffect ( ( ) => {
@@ -252,7 +281,6 @@ function PydanticFormContextProvider({
252281 }
253282
254283 setFormInputHistory ( new Map < string , object > ( ) ) ;
255- rhf . reset ( { } ) ;
256284 } , [ apiResponse , isFullFilled , onSuccess , rhf , skipSuccessNotice ] ) ;
257285
258286 // a useeffect for whenever the error response updates
@@ -266,7 +294,6 @@ function PydanticFormContextProvider({
266294
267295 // when we receive a new form from JSON, we fully reset the form
268296 if ( apiResponse ?. form && rawSchema !== apiResponse . form ) {
269- rhf . reset ( { } ) ;
270297 setRawSchema ( apiResponse . form ) ;
271298 if ( apiResponse . meta ) {
272299 setHasNext ( ! ! apiResponse . meta . hasNext ) ;
@@ -282,7 +309,6 @@ function PydanticFormContextProvider({
282309 setIsSending ( false ) ;
283310 // eslint-disable-next-line react-hooks/exhaustive-deps
284311 } , [ apiResponse ] ) ; // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
285-
286312 // a useeffect for filling data whenever formdefinition or labels update
287313
288314 useEffect ( ( ) => {
@@ -309,112 +335,20 @@ function PydanticFormContextProvider({
309335 } ) ;
310336 } , [ error ] ) ;
311337
312- const submitFormFn = useCallback ( ( ) => {
313- setIsSending ( true ) ;
314- const rhfValues = rhf . getValues ( ) ;
315- // Note. If we don't use cloneDeep here we are adding a reference to the rhfValues
316- // that changes on every change in the form and triggering effects before we want to.
317- addFormInputData ( _ . cloneDeep ( rhfValues ) , ! ! errorDetails ) ;
318- window . scrollTo ( 0 , 0 ) ;
319- } , [ rhf , errorDetails , addFormInputData ] ) ;
320-
321- const onClientSideError = useCallback (
322- ( data ?: FieldValues ) => {
323- // TODO implement save with errors toggle
324- if ( data ) {
325- rhf . clearErrors ( ) ;
326- submitFormFn ( ) ;
327- }
328- } ,
329- [ rhf , submitFormFn ] ,
330- ) ;
331-
332- const submitForm = rhf . handleSubmit ( submitFormFn , onClientSideError ) ;
333-
334- const resetForm = useCallback (
335- ( e : React . MouseEvent < HTMLButtonElement , MouseEvent > ) => {
336- e . preventDefault ( ) ;
337- setErrorDetails ( undefined ) ;
338- rhf . reset ( ) ;
339- rhf . trigger ( ) ;
340- } ,
341- [ rhf ] ,
342- ) ;
343-
344- const resetErrorDetails = useCallback ( ( ) => {
345- setErrorDetails ( undefined ) ;
346- } , [ ] ) ;
347-
348- // with this we have the possibility to have listeners for specific fields
349- // this could be used to trigger validations of related fields, casting changes to elsewhere, etc.
350338 useEffect ( ( ) => {
351- let sub : Subscription ;
352-
353- if ( onFieldChangeHandler ) {
354- sub = rhf . watch ( ( value , { name, type } ) => {
355- onFieldChangeHandler (
356- {
357- name,
358- type,
359- value,
360- } ,
361- rhf ,
362- ) ;
363- } ) ;
364- }
365-
366- return ( ) => {
367- if ( sub ) {
368- return sub . unsubscribe ( ) ;
339+ const getLocale = ( ) => {
340+ switch ( locale ) {
341+ case Locale . enGB :
342+ return z . locales . en ( ) ;
343+ case Locale . nlNL :
344+ return z . locales . nl ( ) ;
345+ default :
346+ return z . locales . en ( ) ;
369347 }
370348 } ;
371- } , [ rhf , onFieldChangeHandler ] ) ;
372349
373- const isLoading =
374- isLoadingFormLabels ||
375- isLoadingSchema ||
376- isParsingSchema ||
377- ( customDataProvider ? isLoadingCustomData : false ) ;
378-
379- const clearForm = useCallback ( ( ) => {
380- setFormInputData ( [ ] ) ;
381- setIsFullFilled ( false ) ;
382- setRawSchema ( undefined ) ;
383- setHasNext ( false ) ;
384- } , [ ] ) ;
385-
386- const PydanticFormContextState = {
387- // to prevent an issue where the sending state hangs
388- // we check both the SWR hook state as our manual state
389- isSending : isSending && isLoadingSchema ,
390- isLoading,
391- rhf,
392- pydanticFormSchema,
393- loadingComponent,
394- onPrevious : ( ) => goToPreviousStep ( rhf ?. getValues ( ) ) ,
395- onCancel,
396- title,
397- sendLabel,
398- isFullFilled,
399- customDataProvider,
400- errorDetails,
401- resetErrorDetails,
402- successNotice,
403- submitForm,
404- resetForm,
405- cancelButton,
406- skipSuccessNotice,
407- allowUntouchedSubmit,
408- resetButtonAlternative,
409- config,
410- setSaveToLeavePageInCurrentState,
411- formKey,
412- formIdKey,
413- clearForm,
414- formInputData,
415- hasNext,
416- initialData,
417- } ;
350+ z . config ( getLocale ( ) ) ;
351+ } , [ locale ] ) ;
418352
419353 return (
420354 < PydanticFormContext . Provider value = { PydanticFormContextState } >
0 commit comments