Skip to content

Commit ea22b77

Browse files
authored
Merge pull request #176 from workfloworchestrator/2134-test-findings
2134 test findings
2 parents c99d8aa + 41646dd commit ea22b77

File tree

2 files changed

+84
-87
lines changed

2 files changed

+84
-87
lines changed

frontend/packages/pydantic-forms/src/components/render/RenderForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const RenderForm = (contextProps: PydanticFormContextProps) => {
4848
}
4949

5050
if (isFullFilled) {
51-
return <div>{t('successfullySent')}</div>;
51+
return <></>;
5252
}
5353

5454
const FormRenderer = formRenderer ?? Form;

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

Lines changed: 83 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)