Skip to content

Commit 5401a64

Browse files
authored
(fix) Properly sanitize and initialize cloned fields (#429)
* Properly sanitize and initialize cloned fields * Fix tests
1 parent c175478 commit 5401a64

File tree

6 files changed

+37
-22
lines changed

6 files changed

+37
-22
lines changed

src/components/inputs/text/text.component.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ const TextField: React.FC<FormFieldInputProps> = ({ field, value, errors, warnin
4242
<TextInput
4343
id={field.id}
4444
labelText={<FieldLabel field={field} />}
45-
onChange={setFieldValue}
45+
onChange={(event) => {
46+
setFieldValue(event.target.value);
47+
}}
4648
onBlur={onBlur}
4749
name={field.id}
4850
value={value}

src/components/inputs/text/text.test.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,11 @@ describe('Text field input', () => {
161161
it('should record new obs', async () => {
162162
await renderForm(textValues);
163163
const inputField = screen.getByLabelText('Indicate your notes');
164-
165-
await user.type(inputField, 'Updated patient notes');
164+
await user.click(inputField);
165+
await user.paste('Updated patient notes');
166166

167167
await act(async () => {
168-
expect(mockSetFieldValue).toHaveBeenCalledWith(
169-
expect.objectContaining({
170-
target: expect.objectContaining({
171-
value: 'Updated patient notes',
172-
}),
173-
}),
174-
);
168+
expect(mockSetFieldValue).toHaveBeenCalledWith('Updated patient notes');
175169
});
176170
});
177171

src/components/renderer/form/form-renderer.component.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { formStateReducer, initialState } from './state';
66
import { useEvaluateFormFieldExpressions } from '../../../hooks/useEvaluateFormFieldExpressions';
77
import { useFormFactory } from '../../../provider/form-factory-provider';
88
import { FormProvider, type FormContextProps } from '../../../provider/form-provider';
9-
import { isTrue } from '../../../utils/boolean-utils';
109
import { type FormProcessorContextProps } from '../../../types';
1110
import { useFormStateHelpers } from '../../../hooks/useFormStateHelpers';
1211
import { pageObserver } from '../../sidebar/page-observer';

src/components/repeat/helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export function cloneRepeatField(srcField: FormField, value: OpenmrsResource, id
1515
childField.id = `${childField.id}_${idSuffix}`;
1616
childField.meta.groupId = clonedField.id;
1717
childField.meta.previousValue = null;
18+
childField.fieldDependents = new Set();
1819
clearSubmission(childField);
1920

2021
// cleanup expressions

src/components/repeat/repeat.component.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@ const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
4747

4848
const handleAdd = useCallback(
4949
(counter: number) => {
50+
const clonedFieldsBuffer: FormField[] = [];
5051
function evaluateExpressions(field: FormField) {
5152
if (field.hide?.hideWhenExpression) {
5253
field.isHidden = evaluateExpression(
5354
field.hide.hideWhenExpression,
5455
{ value: field, type: 'field' },
55-
formFields,
56+
[...formFields, ...clonedFieldsBuffer],
5657
getValues(),
5758
{
5859
mode: sessionMode,
@@ -64,7 +65,7 @@ const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
6465
evaluateAsyncExpression(
6566
field.questionOptions.calculate?.calculateExpression,
6667
{ value: field, type: 'field' },
67-
formFields,
68+
[...formFields, ...clonedFieldsBuffer],
6869
getValues(),
6970
{
7071
mode: sessionMode,
@@ -80,16 +81,19 @@ const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
8081
}
8182

8283
const clonedField = cloneRepeatField(field, null, counter);
83-
// run necessary expressions
84+
clonedFieldsBuffer.push(clonedField);
85+
86+
// Handle nested questions
8487
if (clonedField.type === 'obsGroup') {
8588
clonedField.questions?.forEach((childField) => {
86-
evaluateExpressions(childField);
87-
addFormField(childField);
89+
clonedFieldsBuffer.push(childField);
8890
});
89-
} else {
90-
evaluateExpressions(clonedField);
9191
}
92-
addFormField(clonedField);
92+
93+
clonedFieldsBuffer.forEach((field) => {
94+
evaluateExpressions(field);
95+
addFormField(field);
96+
});
9397
setRows([...rows, clonedField]);
9498
},
9599
[formFields, field, rows, context],

src/hooks/useFormStateHelpers.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,24 @@ export function useFormStateHelpers(dispatch: Dispatch<Action>, formFields: Form
88
const addFormField = useCallback((field: FormField) => {
99
dispatch({ type: 'ADD_FORM_FIELD', value: field });
1010
}, []);
11-
const updateFormField = useCallback((field: FormField) => {
12-
dispatch({ type: 'UPDATE_FORM_FIELD', value: cloneDeep(field) });
13-
}, []);
11+
const updateFormField = useCallback(
12+
(field: FormField) => {
13+
if (field.meta.groupId) {
14+
const group = formFields.find((f) => f.id === field.meta.groupId);
15+
if (group) {
16+
group.questions = group.questions.map((child) => {
17+
if (child.id === field.id) {
18+
return field;
19+
}
20+
return child;
21+
});
22+
updateFormField(group);
23+
}
24+
}
25+
dispatch({ type: 'UPDATE_FORM_FIELD', value: cloneDeep(field) });
26+
},
27+
[formFields],
28+
);
1429

1530
const getFormField = useCallback(
1631
(fieldId: string) => {

0 commit comments

Comments
 (0)