Skip to content

Commit c9725c8

Browse files
author
Ruben van Leeuwen
committed
2134: Reintroduce restoringHistory. Rearrange awaitReset
1 parent 544324f commit c9725c8

File tree

1 file changed

+73
-53
lines changed

1 file changed

+73
-53
lines changed

frontend/packages/pydantic-forms/src/core/PydanticFormContextProvider.tsx

Lines changed: 73 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -77,22 +77,17 @@ function PydanticFormContextProvider({
7777
const formRef = useRef<string>(formKey);
7878

7979
const updateHistory = useCallback(
80-
async (formInput: object, previousSteps: object[]) => {
81-
const hashOfPreviousSteps = await getHashForArray(previousSteps);
82-
setFormInputHistory((prevState) =>
83-
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),
8486
);
8587
},
8688
[],
8789
);
8890

89-
const goToPreviousStep = (formInput: object) => {
90-
setFormInputData((prevState) => {
91-
updateHistory(formInput, prevState);
92-
return prevState.slice(0, -1);
93-
});
94-
};
95-
9691
const [errorDetails, setErrorDetails] =
9792
useState<PydanticFormValidationErrorDetails>();
9893

@@ -168,36 +163,38 @@ function PydanticFormContextProvider({
168163
before proceeding. If it finds data in formHistory based on the hash of the previo
169164
us steps, it uses that data to prefill the form.
170165
*/
171-
const awaitReset = useCallback(async (payLoad?: FieldValues) => {
172-
await getHashForArray(formInputData)
173-
.then((hash) => {
174-
let resetPayload = {};
175-
176-
if (payLoad) {
177-
resetPayload = { ...payLoad };
178-
}
179-
180-
reactHookForm.reset(resetPayload);
181-
})
182-
.catch(() => {
183-
console.error('Failed to hash form input data');
184-
});
185-
}, []);
166+
const awaitReset = useCallback(
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+
}
181+
},
182+
[reactHookForm],
183+
);
186184

187185
const addFormInputData = useCallback(() => {
188-
setFormInputData((currentInputs) => {
186+
setFormInputData((currentFormInputData) => {
189187
// Note. If we don't use cloneDeep here we are adding a reference to the reactHookFormValues
190188
// that changes on every change in the form and triggering effects before we want to.
191189
const reactHookFormValues = _.cloneDeep(reactHookForm.getValues());
192-
updateHistory(reactHookFormValues, currentInputs);
193-
return [...currentInputs, { ...reactHookFormValues }];
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.
195+
awaitReset();
196+
return [...currentFormInputData, { ...reactHookFormValues }];
194197
});
195-
// setInErrorState(false);
196-
// We call reset right after using the values to make sure
197-
// values in reactHookForm are cleared. This avoids some
198-
// race condition errors where reacfHookForm data was still set where we did not
199-
/// expect it to be.
200-
awaitReset();
201198
}, []);
202199

203200
const submitFormFn = useCallback(() => {
@@ -217,6 +214,16 @@ function PydanticFormContextProvider({
217214
[reactHookForm, submitFormFn],
218215
);
219216

217+
const goToPreviousStep = () => {
218+
setFormInputData((currentFormInputData) => {
219+
// Stores any data that is entered but not submitted yet to be
220+
// able to restore later
221+
const reactHookFormValues = _.cloneDeep(reactHookForm.getValues());
222+
updateHistory(currentFormInputData, reactHookFormValues);
223+
return currentFormInputData.slice(0, -1);
224+
});
225+
};
226+
220227
const submitForm = reactHookForm.handleSubmit(
221228
submitFormFn,
222229
onClientSideError,
@@ -287,7 +294,7 @@ function PydanticFormContextProvider({
287294
isLoading,
288295
isSending: isSending && isLoadingSchema,
289296
onCancel,
290-
onPrevious: () => goToPreviousStep(reactHookForm?.getValues()),
297+
onPrevious: () => goToPreviousStep(),
291298
pydanticFormSchema,
292299
reactHookForm,
293300
resetForm,
@@ -297,19 +304,34 @@ function PydanticFormContextProvider({
297304

298305
// useEffect to handle API responses
299306
useEffect(() => {
307+
const restoreHistory = async () => {
308+
await getHashForArray(formInputData)
309+
.then((hash) => {
310+
if (formInputHistory.has(hash)) {
311+
awaitReset(formInputHistory.get(hash) as FieldValues);
312+
} else {
313+
awaitReset();
314+
}
315+
})
316+
.catch(() => {
317+
console.error('Failed to hash form input data');
318+
});
319+
};
320+
300321
if (!apiResponse) {
301322
return;
302323
}
303-
// when we receive errors, we append to the scheme
324+
304325
if (apiResponse?.validation_errors) {
305-
// Restore the data we got the error with
306-
const errorPayload = [...formInputData].pop();
326+
// Restore the data we got the error with and remove it from
327+
// formInputData so we can add it again
307328
setFormInputData((currentData) => {
308-
const newData = [...currentData];
309-
newData.pop();
310-
return newData;
329+
const nextData = [...currentData];
330+
const errorPayload = nextData.pop();
331+
awaitReset(errorPayload);
332+
return nextData;
311333
});
312-
awaitReset(errorPayload);
334+
313335
setErrorDetails(getErrorDetailsFromResponse(apiResponse));
314336
return;
315337
}
@@ -319,22 +341,20 @@ function PydanticFormContextProvider({
319341
return;
320342
}
321343

322-
// when we receive a new form from JSON, we fully reset the form
323344
if (apiResponse?.form && rawSchema !== apiResponse.form) {
324-
awaitReset();
325345
setRawSchema(apiResponse.form);
346+
setErrorDetails(undefined);
326347
if (apiResponse.meta) {
327348
setHasNext(!!apiResponse.meta.hasNext);
328349
}
329-
330-
setErrorDetails(undefined);
350+
restoreHistory();
331351
}
332352

333353
setIsSending(false);
334354
// eslint-disable-next-line react-hooks/exhaustive-deps
335355
}, [apiResponse]); // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
336356

337-
// Useeffect to the form input data if the formKey changes
357+
// useEffect to the form input data if the formKey changes
338358
useEffect(() => {
339359
if (formKey !== formRef.current) {
340360
setFormInputData([]);
@@ -343,22 +363,22 @@ function PydanticFormContextProvider({
343363
}
344364
}, [formKey]);
345365

346-
// UseEffect to handle successfull submits
366+
// useEffect to handle successfull submits
347367
useEffect(() => {
348368
if (!isFullFilled) {
349369
return;
350370
}
351371

352372
if (onSuccess) {
353-
const values = reactHookForm.getValues();
354-
onSuccess(values, apiResponse || {});
373+
const reactHookFormValues = _.cloneDeep(reactHookForm.getValues());
374+
onSuccess(reactHookFormValues, apiResponse || {});
355375
}
356376

357377
setFormInputHistory(new Map<string, object>());
358378
// eslint-disable-next-line react-hooks/exhaustive-deps
359379
}, [isFullFilled]); // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
360380

361-
// UseEffect to handles errors throws by the useApiProvider call
381+
// useEffect to handles errors throws by the useApiProvider call
362382
// for instance unexpected 500 errors
363383
useEffect(() => {
364384
if (!error) {
@@ -372,7 +392,7 @@ function PydanticFormContextProvider({
372392
});
373393
}, [error]);
374394

375-
// UseEffect to handle locale change
395+
// useEffect to handle locale change
376396
useEffect(() => {
377397
const getLocale = () => {
378398
switch (locale) {

0 commit comments

Comments
 (0)