@@ -74,26 +74,20 @@ function PydanticFormContextProvider({
7474 new Map < string , object > ( ) ,
7575 ) ;
7676 const [ formInputData , setFormInputData ] = useState < object [ ] > ( [ ] ) ;
77-
7877 const formRef = useRef < string > ( formKey ) ;
7978
8079 const updateHistory = useCallback (
81- async ( formInput : object , previousSteps : object [ ] ) => {
82- const hashOfPreviousSteps = await getHashForArray ( previousSteps ) ;
83- setFormInputHistory ( ( prevState ) =>
84- prevState . set ( hashOfPreviousSteps , formInput ) ,
80+ async ( previousStepsData : object [ ] , formInputData : object ) => {
81+ const hashOfPreviousSteps = await getHashForArray (
82+ previousStepsData ,
83+ ) ;
84+ setFormInputHistory ( ( currentState ) =>
85+ currentState . set ( hashOfPreviousSteps , formInputData ) ,
8586 ) ;
8687 } ,
8788 [ ] ,
8889 ) ;
8990
90- const goToPreviousStep = ( formInput : object ) => {
91- setFormInputData ( ( prevState ) => {
92- updateHistory ( formInput , prevState ) ;
93- return prevState . slice ( 0 , - 1 ) ;
94- } ) ;
95- } ;
96-
9791 const [ errorDetails , setErrorDetails ] =
9892 useState < PydanticFormValidationErrorDetails > ( ) ;
9993
@@ -166,58 +160,49 @@ function PydanticFormContextProvider({
166160
167161 /*
168162 This method resets the form and makes sure it waits for the reset to complete
169- before proceeding. If it finds data in form history, it uses that data to reset the form.
163+ before proceeding. If it finds data in formHistory based on the hash of the previo
164+ us steps, it uses that data to prefill the form.
170165 */
171166 const awaitReset = useCallback (
172- async ( payLoad ?: FieldValues ) => {
173- await getHashForArray ( formInputData ) . then ( ( hash ) => {
174- let resetPayload = { } ;
175-
176- if ( payLoad ) {
177- resetPayload = { ...payLoad } ;
178- } else {
179- const currentStepFromHistory = formInputHistory . get ( hash ) ;
180-
181- if ( currentStepFromHistory ) {
182- resetPayload = { ...currentStepFromHistory } ;
183- }
184- }
185- reactHookForm . reset ( resetPayload ) ;
186- } ) ;
167+ async ( payLoad : FieldValues = { } ) => {
168+ try {
169+ reactHookForm . reset ( payLoad ) ;
170+
171+ // This is a workaround to we wait for the reset to complete
172+ // https://gemini.google.com/app/26d8662d603d6322?hl=nl
173+ return new Promise < void > ( ( resolve ) => {
174+ setTimeout ( ( ) => {
175+ resolve ( ) ;
176+ } , 0 ) ;
177+ } ) ;
178+ } catch ( error ) {
179+ console . error ( 'Failed to reactHookFOrm' , error ) ;
180+ }
187181 } ,
188-
189- [ formInputData , formInputHistory , reactHookForm ] ,
182+ [ reactHookForm ] ,
190183 ) ;
191184
192- const addFormInputData = useCallback (
193- ( formInput : object , replaceInsteadOfAdd = false ) => {
194- setFormInputData ( ( currentInputs ) => {
195- const data = replaceInsteadOfAdd
196- ? currentInputs . slice ( 0 , - 1 )
197- : currentInputs ;
198- updateHistory ( formInput , data ) ;
199- return [ ...data , { ...formInput } ] ;
200- } ) ;
185+ const addFormInputData = useCallback ( ( ) => {
186+ setFormInputData ( ( currentFormInputData ) => {
187+ // Note. If we don't use cloneDeep here we are adding a reference to the reactHookFormValues
188+ // that changes on every change in the form and triggering effects before we want to.
189+ const reactHookFormValues = _ . cloneDeep ( reactHookForm . getValues ( ) ) ;
190+ updateHistory ( currentFormInputData , reactHookFormValues ) ;
191+ // We call reset right after using the values to make sure
192+ // values in reactHookForm are cleared. This avoids some
193+ // race condition errors where reactHookForm data was still set where
194+ // we did not expect it to be.
201195 awaitReset ( ) ;
202- } ,
203- [ awaitReset , setFormInputData , updateHistory ] ,
204- ) ;
196+ return [ ... currentFormInputData , { ... reactHookFormValues } ] ;
197+ } ) ;
198+ } , [ ] ) ;
205199
206200 const submitFormFn = useCallback ( ( ) => {
207201 setIsSending ( true ) ;
208- const reactHookFormValues = _ . cloneDeep ( reactHookForm . getValues ( ) ) ;
209- awaitReset ( ) ;
210- // Note. If we don't use cloneDeep here we are adding a reference to the reactHookFormValues
211- // that changes on every change in the form and triggering effects before we want to.
212- addFormInputData ( reactHookFormValues , ! ! errorDetails ) ;
202+ setErrorDetails ( undefined ) ;
203+ addFormInputData ( ) ;
213204 window . scrollTo ( 0 , 0 ) ;
214- } , [
215- reactHookForm ,
216- errorDetails ,
217- addFormInputData ,
218- awaitReset ,
219- setIsSending ,
220- ] ) ;
205+ } , [ ] ) ;
221206
222207 const onClientSideError = useCallback (
223208 ( data ?: FieldValues ) => {
@@ -230,6 +215,16 @@ function PydanticFormContextProvider({
230215 [ reactHookForm , submitFormFn ] ,
231216 ) ;
232217
218+ const goToPreviousStep = ( ) => {
219+ setFormInputData ( ( currentFormInputData ) => {
220+ // Stores any data that is entered but not submitted yet to be
221+ // able to restore later
222+ const reactHookFormValues = _ . cloneDeep ( reactHookForm . getValues ( ) ) ;
223+ updateHistory ( currentFormInputData , reactHookFormValues ) ;
224+ return currentFormInputData . slice ( 0 , - 1 ) ;
225+ } ) ;
226+ } ;
227+
233228 const submitForm = reactHookForm . handleSubmit (
234229 submitFormFn ,
235230 onClientSideError ,
@@ -245,23 +240,12 @@ function PydanticFormContextProvider({
245240 [ awaitReset , reactHookForm ] ,
246241 ) ;
247242
248- const resetErrorDetails = useCallback ( ( ) => {
249- setErrorDetails ( undefined ) ;
250- } , [ ] ) ;
251-
252243 const isLoading =
253244 isLoadingFormLabels ||
254245 isLoadingSchema ||
255246 isParsingSchema ||
256247 ( customDataProvider ? isLoadingCustomData : false ) ;
257248
258- const clearForm = useCallback ( ( ) => {
259- setFormInputData ( [ ] ) ;
260- setIsFullFilled ( false ) ;
261- setRawSchema ( emptyRawSchema ) ;
262- setHasNext ( false ) ;
263- } , [ emptyRawSchema ] ) ;
264-
265249 const fieldDataStorageRef = useRef < Map < string , Map < string , unknown > > > (
266250 new Map ( ) ,
267251 ) ;
@@ -299,9 +283,6 @@ function PydanticFormContextProvider({
299283 ) ;
300284
301285 const PydanticFormContextState = {
302- // to prevent an issue where the sending state hangs
303- // we check both the SWR hook state as our manual state
304- clearForm,
305286 config,
306287 customDataProvider,
307288 errorDetails,
@@ -314,74 +295,90 @@ function PydanticFormContextProvider({
314295 isLoading,
315296 isSending : isSending && isLoadingSchema ,
316297 onCancel,
317- onPrevious : ( ) => goToPreviousStep ( reactHookForm ?. getValues ( ) ) ,
298+ onPrevious : ( ) => goToPreviousStep ( ) ,
318299 pydanticFormSchema,
319300 reactHookForm,
320- resetErrorDetails,
321301 resetForm,
322302 submitForm,
323303 title,
324304 } ;
325305
326306 // useEffect to handle API responses
327307 useEffect ( ( ) => {
308+ const restoreHistory = async ( ) => {
309+ await getHashForArray ( formInputData )
310+ . then ( ( hash ) => {
311+ if ( formInputHistory . has ( hash ) ) {
312+ awaitReset ( formInputHistory . get ( hash ) as FieldValues ) ;
313+ } else {
314+ awaitReset ( ) ;
315+ }
316+ } )
317+ . catch ( ( ) => {
318+ console . error ( 'Failed to hash form input data' ) ;
319+ } ) ;
320+ } ;
321+
328322 if ( ! apiResponse ) {
329323 return ;
330324 }
331- // when we receive errors, we append to the scheme
325+
332326 if ( apiResponse ?. validation_errors ) {
333- // Restore the data we got the error with
334- const errorPayload = [ ...formInputData ] . pop ( ) ;
335- awaitReset ( errorPayload ) ;
327+ // Restore the data we got the error with and remove it from
328+ // formInputData so we can add it again
329+ setFormInputData ( ( currentData ) => {
330+ const nextData = [ ...currentData ] ;
331+ const errorPayload = nextData . pop ( ) ;
332+ awaitReset ( errorPayload ) ;
333+ return nextData ;
334+ } ) ;
335+
336336 setErrorDetails ( getErrorDetailsFromResponse ( apiResponse ) ) ;
337337 return ;
338338 }
339339
340- awaitReset ( ) ;
341340 if ( apiResponse ?. success ) {
342341 setIsFullFilled ( true ) ;
343342 return ;
344343 }
345344
346- // when we receive a new form from JSON, we fully reset the form
347345 if ( apiResponse ?. form && rawSchema !== apiResponse . form ) {
348346 setRawSchema ( apiResponse . form ) ;
349347 if ( apiResponse . meta ) {
350348 setHasNext ( ! ! apiResponse . meta . hasNext ) ;
351349 }
352- setErrorDetails ( undefined ) ;
350+ restoreHistory ( ) ;
353351 }
354352
355353 setIsSending ( false ) ;
356354 // eslint-disable-next-line react-hooks/exhaustive-deps
357355 } , [ apiResponse ] ) ; // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
358356
359- // Useeffect to the form input data if the formKey changes
357+ // useEffect to the form input data if the formKey changes
360358 useEffect ( ( ) => {
361359 if ( formKey !== formRef . current ) {
362360 setFormInputData ( [ ] ) ;
363361 setFormInputHistory ( new Map < string , object > ( ) ) ;
364- awaitReset ( { } ) ;
365362 formRef . current = formKey ;
366363 }
367- } , [ awaitReset , formKey ] ) ;
364+ } , [ formKey ] ) ;
368365
369- // UseEffect to handle successfull submits
366+ // useEffect to handle successfull submits
370367 useEffect ( ( ) => {
371368 if ( ! isFullFilled ) {
372369 return ;
373370 }
374371
375372 if ( onSuccess ) {
376- const values = reactHookForm . getValues ( ) ;
377- onSuccess ( values , apiResponse || { } ) ;
373+ const reactHookFormValues = _ . cloneDeep ( reactHookForm . getValues ( ) ) ;
374+ onSuccess ( reactHookFormValues , apiResponse || { } ) ;
378375 }
379376
380377 setFormInputHistory ( new Map < string , object > ( ) ) ;
381378 // eslint-disable-next-line react-hooks/exhaustive-deps
382- } , [ apiResponse , isFullFilled ] ) ; // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
379+ } , [ isFullFilled ] ) ; // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
383380
384- // UseEffect to handles errors throws by the useApiProvider call
381+ // useEffect to handles errors throws by the useApiProvider call
385382 // for instance unexpected 500 errors
386383 useEffect ( ( ) => {
387384 if ( ! error ) {
@@ -395,7 +392,7 @@ function PydanticFormContextProvider({
395392 } ) ;
396393 } , [ error ] ) ;
397394
398- // UseEffect to handle locale change
395+ // useEffect to handle locale change
399396 useEffect ( ( ) => {
400397 const getLocale = ( ) => {
401398 switch ( locale ) {
0 commit comments