Skip to content

Commit 544324f

Browse files
author
Ruben van Leeuwen
committed
2134: Fix and simplify data handling when an error occurs
1 parent baffc5d commit 544324f

File tree

1 file changed

+36
-59
lines changed

1 file changed

+36
-59
lines changed

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

Lines changed: 36 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ 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(
@@ -166,58 +165,46 @@ function PydanticFormContextProvider({
166165

167166
/*
168167
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.
168+
before proceeding. If it finds data in formHistory based on the hash of the previo
169+
us steps, it uses that data to prefill the form.
170170
*/
171-
const awaitReset = useCallback(
172-
async (payLoad?: FieldValues) => {
173-
await getHashForArray(formInputData).then((hash) => {
171+
const awaitReset = useCallback(async (payLoad?: FieldValues) => {
172+
await getHashForArray(formInputData)
173+
.then((hash) => {
174174
let resetPayload = {};
175175

176176
if (payLoad) {
177177
resetPayload = { ...payLoad };
178-
} else {
179-
const currentStepFromHistory = formInputHistory.get(hash);
180-
181-
if (currentStepFromHistory) {
182-
resetPayload = { ...currentStepFromHistory };
183-
}
184178
}
179+
185180
reactHookForm.reset(resetPayload);
181+
})
182+
.catch(() => {
183+
console.error('Failed to hash form input data');
186184
});
187-
},
188-
189-
[formInputData, formInputHistory, reactHookForm],
190-
);
185+
}, []);
191186

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-
});
201-
awaitReset();
202-
},
203-
[awaitReset, setFormInputData, updateHistory],
204-
);
187+
const addFormInputData = useCallback(() => {
188+
setFormInputData((currentInputs) => {
189+
// Note. If we don't use cloneDeep here we are adding a reference to the reactHookFormValues
190+
// that changes on every change in the form and triggering effects before we want to.
191+
const reactHookFormValues = _.cloneDeep(reactHookForm.getValues());
192+
updateHistory(reactHookFormValues, currentInputs);
193+
return [...currentInputs, { ...reactHookFormValues }];
194+
});
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();
201+
}, []);
205202

206203
const submitFormFn = useCallback(() => {
207204
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);
205+
addFormInputData();
213206
window.scrollTo(0, 0);
214-
}, [
215-
reactHookForm,
216-
errorDetails,
217-
addFormInputData,
218-
awaitReset,
219-
setIsSending,
220-
]);
207+
}, []);
221208

222209
const onClientSideError = useCallback(
223210
(data?: FieldValues) => {
@@ -245,23 +232,12 @@ function PydanticFormContextProvider({
245232
[awaitReset, reactHookForm],
246233
);
247234

248-
const resetErrorDetails = useCallback(() => {
249-
setErrorDetails(undefined);
250-
}, []);
251-
252235
const isLoading =
253236
isLoadingFormLabels ||
254237
isLoadingSchema ||
255238
isParsingSchema ||
256239
(customDataProvider ? isLoadingCustomData : false);
257240

258-
const clearForm = useCallback(() => {
259-
setFormInputData([]);
260-
setIsFullFilled(false);
261-
setRawSchema(emptyRawSchema);
262-
setHasNext(false);
263-
}, [emptyRawSchema]);
264-
265241
const fieldDataStorageRef = useRef<Map<string, Map<string, unknown>>>(
266242
new Map(),
267243
);
@@ -299,9 +275,6 @@ function PydanticFormContextProvider({
299275
);
300276

301277
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,
305278
config,
306279
customDataProvider,
307280
errorDetails,
@@ -317,7 +290,6 @@ function PydanticFormContextProvider({
317290
onPrevious: () => goToPreviousStep(reactHookForm?.getValues()),
318291
pydanticFormSchema,
319292
reactHookForm,
320-
resetErrorDetails,
321293
resetForm,
322294
submitForm,
323295
title,
@@ -332,23 +304,29 @@ function PydanticFormContextProvider({
332304
if (apiResponse?.validation_errors) {
333305
// Restore the data we got the error with
334306
const errorPayload = [...formInputData].pop();
307+
setFormInputData((currentData) => {
308+
const newData = [...currentData];
309+
newData.pop();
310+
return newData;
311+
});
335312
awaitReset(errorPayload);
336313
setErrorDetails(getErrorDetailsFromResponse(apiResponse));
337314
return;
338315
}
339316

340-
awaitReset();
341317
if (apiResponse?.success) {
342318
setIsFullFilled(true);
343319
return;
344320
}
345321

346322
// when we receive a new form from JSON, we fully reset the form
347323
if (apiResponse?.form && rawSchema !== apiResponse.form) {
324+
awaitReset();
348325
setRawSchema(apiResponse.form);
349326
if (apiResponse.meta) {
350327
setHasNext(!!apiResponse.meta.hasNext);
351328
}
329+
352330
setErrorDetails(undefined);
353331
}
354332

@@ -361,10 +339,9 @@ function PydanticFormContextProvider({
361339
if (formKey !== formRef.current) {
362340
setFormInputData([]);
363341
setFormInputHistory(new Map<string, object>());
364-
awaitReset({});
365342
formRef.current = formKey;
366343
}
367-
}, [awaitReset, formKey]);
344+
}, [formKey]);
368345

369346
// UseEffect to handle successfull submits
370347
useEffect(() => {
@@ -379,7 +356,7 @@ function PydanticFormContextProvider({
379356

380357
setFormInputHistory(new Map<string, object>());
381358
// eslint-disable-next-line react-hooks/exhaustive-deps
382-
}, [apiResponse, isFullFilled]); // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
359+
}, [isFullFilled]); // Avoid completing the dependencies array here to avoid unwanted resetFormData calls
383360

384361
// UseEffect to handles errors throws by the useApiProvider call
385362
// for instance unexpected 500 errors

0 commit comments

Comments
 (0)