Skip to content

Commit 40e4267

Browse files
authored
fix(orchestrator): endless onChange() loop and turning ui:allowNewItems from string to boolean type (#1720)
Signed-off-by: Marek Libra <marek.libra@gmail.com>
1 parent 9e1f179 commit 40e4267

7 files changed

Lines changed: 52 additions & 22 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-widgets': patch
3+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch
4+
---
5+
6+
Fixing endless onChange() loop and turning "ui:allowNewItems" from string to boolean type.

workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorForm.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -159,18 +159,17 @@ const OrchestratorForm = ({
159159
return generateUiSchema(schema, isMultiStep);
160160
}, [schema, isMultiStep]);
161161

162-
const reviewStep = useMemo(
163-
() => (
162+
const reviewStep = useMemo(() => {
163+
return (
164164
<ReviewStep
165165
data={prunedFormData}
166166
schema={schema}
167167
busy={isExecuting}
168168
handleExecute={_handleExecute}
169169
// no schema update here
170170
/>
171-
),
172-
[prunedFormData, schema, isExecuting, _handleExecute],
173-
);
171+
);
172+
}, [prunedFormData, schema, isExecuting, _handleExecute]);
174173

175174
return (
176175
<StepperContextProvider reviewStep={reviewStep} t={t}>

workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormWrapper.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { ErrorPanel } from '@backstage/core-components';
2020
import { JsonObject } from '@backstage/types';
2121

2222
import Grid from '@mui/material/Grid';
23-
import { withTheme } from '@rjsf/core';
23+
import { IChangeEvent, withTheme } from '@rjsf/core';
2424
import { Theme as MuiTheme } from '@rjsf/material-ui';
2525
import { ErrorSchema } from '@rjsf/utils';
2626
import type { JSONSchema7 } from 'json-schema';
@@ -47,7 +47,6 @@ const MuiForm = withTheme<
4747

4848
const FormComponent = (decoratorProps: FormDecoratorProps) => {
4949
const formContext = decoratorProps.formContext;
50-
5150
const [extraErrors, setExtraErrors] = useState<
5251
ErrorSchema<JsonObject> | undefined
5352
>();
@@ -111,6 +110,15 @@ const FormComponent = (decoratorProps: FormDecoratorProps) => {
111110
}
112111
};
113112

113+
const onChange = (
114+
e: IChangeEvent<JsonObject, JSONSchema7, OrchestratorFormContextProps>,
115+
) => {
116+
setFormData(e.formData || {});
117+
if (decoratorProps.onChange) {
118+
decoratorProps.onChange(e);
119+
}
120+
};
121+
114122
return (
115123
<Grid container spacing={2} direction="column" wrap="nowrap">
116124
{validationError && (
@@ -131,12 +139,7 @@ const FormComponent = (decoratorProps: FormDecoratorProps) => {
131139
noHtml5Validate
132140
extraErrors={extraErrors}
133141
onSubmit={e => onSubmit(e.formData || {})}
134-
onChange={e => {
135-
setFormData(e.formData || {});
136-
if (decoratorProps.onChange) {
137-
decoratorProps.onChange(e);
138-
}
139-
}}
142+
onChange={onChange}
140143
>
141144
{children}
142145
</MuiForm>

workspaces/orchestrator/plugins/orchestrator-form-widgets/src/uiPropTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { TypographyVariant } from '@mui/material/styles';
1919
export type UiProps = {
2020
'ui:variant'?: TypographyVariant;
2121
'ui:text'?: string;
22-
'ui:allowNewItems'?: 'true' | 'false';
22+
'ui:allowNewItems'?: boolean;
2323
'fetch:url'?: string;
2424
'fetch:method'?: 'GET' | 'POST';
2525
'fetch:headers'?: Record<string, string>;

workspaces/orchestrator/plugins/orchestrator-form-widgets/src/widgets/ActiveDropdown.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export const ActiveDropdown: Widget<
7070
() => (props.options?.props ?? {}) as UiProps,
7171
[props.options?.props],
7272
);
73-
const isReadOnly = !!props.readonly;
73+
const isReadOnly = !!props?.schema.readOnly;
7474

7575
const labelSelector = uiProps['fetch:response:label']?.toString();
7676
const valueSelector = uiProps['fetch:response:value']?.toString();
@@ -140,7 +140,7 @@ export const ActiveDropdown: Widget<
140140

141141
// set default value to the first one
142142
useEffect(() => {
143-
if (!isChangedByUser && values && values.length > 0) {
143+
if (!isChangedByUser && !value && values && values.length > 0) {
144144
handleChange(values[0], false);
145145
}
146146
}, [handleChange, value, values, isChangedByUser]);

workspaces/orchestrator/plugins/orchestrator-form-widgets/src/widgets/ActiveMultiSelect.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,13 @@ export const ActiveMultiSelect: Widget<
6666
() => (props.options.props ?? {}) as UiProps,
6767
[props.options.props],
6868
);
69-
const isReadOnly = !!props.readonly;
69+
const isReadOnly = !!props?.schema.readOnly;
7070

7171
const autocompleteSelector =
7272
uiProps['fetch:response:autocomplete']?.toString();
7373
const mandatorySelector = uiProps['fetch:response:mandatory']?.toString();
7474
const defaultValueSelector = uiProps['fetch:response:value']?.toString();
75-
const allowNewItems = uiProps['ui:allowNewItems'] === 'true';
76-
75+
const allowNewItems = uiProps['ui:allowNewItems'] === true;
7776
const [localError] = useState<string | undefined>(
7877
autocompleteSelector
7978
? undefined
@@ -124,7 +123,17 @@ export const ActiveMultiSelect: Widget<
124123
true,
125124
true,
126125
);
127-
setAutocompleteOptions(autocompleteValues);
126+
127+
// Only update if arrays differ (by item or count).
128+
const arraysAreEqual =
129+
Array.isArray(autocompleteOptions) &&
130+
autocompleteValues.length === autocompleteOptions.length &&
131+
[...autocompleteValues].sort().join(',') ===
132+
[...autocompleteOptions].sort().join(',');
133+
134+
if (!arraysAreEqual) {
135+
setAutocompleteOptions(autocompleteValues);
136+
}
128137
}
129138

130139
let defaults: string[] = [];
@@ -144,7 +153,17 @@ export const ActiveMultiSelect: Widget<
144153
let mandatory: string[] = [];
145154
if (mandatorySelector) {
146155
mandatory = await applySelectorArray(data, mandatorySelector, true);
147-
setMandatoryValues(mandatory);
156+
157+
// Only update if arrays differ (by item or count).
158+
const arraysAreEqual =
159+
Array.isArray(mandatoryValues) &&
160+
mandatory.length === mandatoryValues.length &&
161+
[...mandatory].sort().join(',') ===
162+
[...mandatoryValues].sort().join(',');
163+
164+
if (!arraysAreEqual) {
165+
setMandatoryValues(mandatory);
166+
}
148167
}
149168

150169
if (
@@ -161,6 +180,8 @@ export const ActiveMultiSelect: Widget<
161180
autocompleteSelector,
162181
mandatorySelector,
163182
defaultValueSelector,
183+
autocompleteOptions,
184+
mandatoryValues,
164185
isChangedByUser,
165186
data,
166187
props.id,

workspaces/orchestrator/plugins/orchestrator-form-widgets/src/widgets/ActiveTextInput.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export const ActiveTextInput: Widget<
6464
() => (props.options?.props ?? {}) as UiProps,
6565
[props.options?.props],
6666
);
67-
const isReadOnly = !!props.readonly;
67+
const isReadOnly = !!props?.schema.readOnly;
6868

6969
const defaultValueSelector = uiProps['fetch:response:value']?.toString();
7070
const autocompleteSelector =
@@ -208,6 +208,7 @@ export const ActiveTextInput: Widget<
208208
data-testid={`${id}-textfield`}
209209
onChange={event => handleChange(event.target.value, true)}
210210
label={label}
211+
disabled={isReadOnly}
211212
/>
212213
</FormControl>
213214
);

0 commit comments

Comments
 (0)