Fix loading sequence for PUI form fields (#7678)

- Ensure that "existing" values are honored
- Change hook execution ordering
- Delay field display until all fields are loaded
This commit is contained in:
Oliver 2024-07-18 14:16:30 +10:00 committed by GitHub
parent ecec81ead0
commit c6d310caf7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 35 additions and 22 deletions

View File

@ -139,6 +139,7 @@ export function OptionsApiForm({
if (!props.ignorePermissionCheck) { if (!props.ignorePermissionCheck) {
fields = extractAvailableFields(response, props.method); fields = extractAvailableFields(response, props.method);
} }
return fields; return fields;
}, },
throwOnError: (error: any) => { throwOnError: (error: any) => {
@ -183,7 +184,7 @@ export function OptionsApiForm({
<ApiForm <ApiForm
id={id} id={id}
props={formProps} props={formProps}
optionsLoading={optionsQuery.isFetching} optionsLoading={optionsQuery.isFetching || !optionsQuery.data}
/> />
); );
} }
@ -206,9 +207,6 @@ export function ApiForm({
const [fields, setFields] = useState<ApiFormFieldSet>( const [fields, setFields] = useState<ApiFormFieldSet>(
() => props.fields ?? {} () => props.fields ?? {}
); );
useEffect(() => {
setFields(props.fields ?? {});
}, [props.fields]);
const defaultValues: FieldValues = useMemo(() => { const defaultValues: FieldValues = useMemo(() => {
let defaultValuesMap = mapFields(fields ?? {}, (_path, field) => { let defaultValuesMap = mapFields(fields ?? {}, (_path, field) => {
@ -314,11 +312,24 @@ export function ApiForm({
} catch (error) { } catch (error) {
console.error('ERR: Error fetching initial data:', error); console.error('ERR: Error fetching initial data:', error);
// Re-throw error to allow react-query to handle error // Re-throw error to allow react-query to handle error
throw error; return {};
} }
} }
}); });
useEffect(() => {
let _fields = props.fields ?? {};
// Ensure default values override initial field spec
for (const k of Object.keys(_fields)) {
if (defaultValues[k]) {
_fields[k].value = defaultValues[k];
}
}
setFields(_fields);
}, [props.fields, defaultValues, initialDataQuery.data]);
// Fetch initial data on form load // Fetch initial data on form load
useEffect(() => { useEffect(() => {
// Fetch initial data if the fetchInitialData property is set // Fetch initial data if the fetchInitialData property is set
@ -559,21 +570,23 @@ export function ApiForm({
)} )}
</Boundary> </Boundary>
<Boundary label={`ApiForm-${id}-FormContent`}> <Boundary label={`ApiForm-${id}-FormContent`}>
<FormProvider {...form}> {!isLoading && (
<Stack gap="xs"> <FormProvider {...form}>
{!optionsLoading && <Stack gap="xs">
Object.entries(fields).map(([fieldName, field]) => ( {!optionsLoading &&
<ApiFormField Object.entries(fields).map(([fieldName, field]) => (
key={fieldName} <ApiFormField
fieldName={fieldName} key={fieldName}
definition={field} fieldName={fieldName}
control={form.control} definition={field}
url={url} control={form.control}
setFields={setFields} url={url}
/> setFields={setFields}
))} />
</Stack> ))}
</FormProvider> </Stack>
</FormProvider>
)}
</Boundary> </Boundary>
<Boundary label={`ApiForm-${id}-PostFormContent`}> <Boundary label={`ApiForm-${id}-PostFormContent`}>
{props.postFormContent} {props.postFormContent}

View File

@ -149,7 +149,7 @@ export function ApiFormField({
label: hideLabels ? undefined : definition.label, label: hideLabels ? undefined : definition.label,
description: hideLabels ? undefined : definition.description description: hideLabels ? undefined : definition.description
}; };
}, [definition]); }, [hideLabels, definition]);
// pull out onValueChange as this can cause strange errors when passing the // pull out onValueChange as this can cause strange errors when passing the
// definition to the input components via spread syntax // definition to the input components via spread syntax
@ -202,7 +202,7 @@ export function ApiFormField({
} }
return val; return val;
}, [value]); }, [definition.field_type, value]);
// Coerce the value to a (stringified) boolean value // Coerce the value to a (stringified) boolean value
const booleanValue: boolean = useMemo(() => { const booleanValue: boolean = useMemo(() => {