Skip to content

Commit f4e333b

Browse files
chore(compass-aggregations): refactors project form, removes the improper use of useEffect (#4352)
1 parent 39d0f06 commit f4e333b

File tree

2 files changed

+67
-44
lines changed

2 files changed

+67
-44
lines changed

packages/compass-aggregations/src/components/aggregation-side-panel/stage-wizard-use-cases/project/project.spec.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -144,20 +144,25 @@ describe('project', function () {
144144
context(`when variant is ${variant}`, function () {
145145
it('should return correct project stage for provided form state', function () {
146146
const op = variant === 'exclude' ? 0 : 1;
147-
expect(mapProjectFormStateToStageValue(variant, [])).to.deep.equal(
148-
{}
149-
);
147+
expect(
148+
mapProjectFormStateToStageValue({
149+
projectionType: variant,
150+
projectionFields: [],
151+
})
152+
).to.deep.equal({});
150153

151154
expect(
152-
mapProjectFormStateToStageValue(variant, ['field1', 'field2'])
155+
mapProjectFormStateToStageValue({
156+
projectionType: variant,
157+
projectionFields: ['field1', 'field2'],
158+
})
153159
).to.deep.equal({ field1: op, field2: op });
154160

155161
expect(
156-
mapProjectFormStateToStageValue(variant, [
157-
'field1',
158-
'field2',
159-
'field1',
160-
])
162+
mapProjectFormStateToStageValue({
163+
projectionType: variant,
164+
projectionFields: ['field1', 'field2', 'field1'],
165+
})
161166
).to.deep.equal({ field1: op, field2: op });
162167
});
163168
});

packages/compass-aggregations/src/components/aggregation-side-panel/stage-wizard-use-cases/project/project.tsx

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useMemo, useState } from 'react';
1+
import React, { useMemo, useState } from 'react';
22

33
import {
44
ComboboxWithCustomOption,
@@ -10,27 +10,34 @@ import {
1010
import type { WizardComponentProps } from '..';
1111

1212
// Types
13-
type ProjectFormState = string[];
13+
type ProjectionFields = string[];
1414
export type ProjectionType = 'include' | 'exclude';
15+
type ProjectFormState = {
16+
projectionFields: ProjectionFields;
17+
projectionType: ProjectionType;
18+
};
1519

1620
// Helpers
1721
export const SELECT_PLACEHOLDER_TEXT = 'Select projection type';
1822
export const COMBOBOX_PLACEHOLDER_TEXT = 'Select field names';
1923

20-
export const mapProjectFormStateToStageValue = (
21-
projectionType: ProjectionType,
22-
formState: ProjectFormState
23-
) => {
24-
return formState.reduce<{ [field: string]: 1 | 0 }>((projection, field) => {
25-
if (field === null) {
26-
return projection;
27-
} else {
28-
return {
29-
...projection,
30-
[field]: projectionType === 'include' ? 1 : 0,
31-
};
32-
}
33-
}, {});
24+
export const mapProjectFormStateToStageValue = ({
25+
projectionFields,
26+
projectionType,
27+
}: ProjectFormState) => {
28+
return projectionFields.reduce<{ [field: string]: 1 | 0 }>(
29+
(projection, field) => {
30+
if (field === null) {
31+
return projection;
32+
} else {
33+
return {
34+
...projection,
35+
[field]: projectionType === 'include' ? 1 : 0,
36+
};
37+
}
38+
},
39+
{}
40+
);
3441
};
3542

3643
// Generates parent paths for a list of paths
@@ -96,27 +103,29 @@ const comboboxStyles = css({ width: '350px' });
96103

97104
const ProjectForm = ({ fields, onChange }: WizardComponentProps) => {
98105
const fieldNames = useMemo(() => fields.map(({ name }) => name), [fields]);
99-
const [projectionType, setProjectionType] =
100-
useState<ProjectionType>('include');
101-
const [projectFields, setProjectFields] = useState<ProjectFormState>([]);
102-
103-
useEffect(() => {
104-
const stageValue = mapProjectFormStateToStageValue(
105-
projectionType,
106-
projectFields
107-
);
108106

107+
const [projectFormState, setProjectFormState] = useState<ProjectFormState>({
108+
projectionType: 'include',
109+
projectionFields: [],
110+
});
111+
112+
const handleProjectFormStateChange = <T extends keyof ProjectFormState>(
113+
property: T,
114+
value: ProjectFormState[T]
115+
) => {
116+
const nextProjectState = {
117+
...projectFormState,
118+
[property]: value,
119+
};
120+
setProjectFormState(nextProjectState);
121+
122+
const stageValue = mapProjectFormStateToStageValue(nextProjectState);
109123
onChange(
110124
JSON.stringify(stageValue),
111125
Object.keys(stageValue).length === 0
112126
? new Error('No field selected')
113127
: null
114128
);
115-
}, [projectFields, onChange, projectionType]);
116-
117-
const onSelectField = (value: string[]) => {
118-
const nextProjectFields = [...value];
119-
setProjectFields(nextProjectFields);
120129
};
121130

122131
return (
@@ -128,8 +137,13 @@ const ProjectForm = ({ fields, onChange }: WizardComponentProps) => {
128137
className={selectStyles}
129138
allowDeselect={false}
130139
aria-label={SELECT_PLACEHOLDER_TEXT}
131-
value={projectionType}
132-
onChange={(value) => setProjectionType(value as ProjectionType)}
140+
value={projectFormState.projectionType}
141+
onChange={(value) =>
142+
handleProjectFormStateChange(
143+
'projectionType',
144+
value as ProjectionType
145+
)
146+
}
133147
>
134148
<Option value="include">Include</Option>
135149
<Option value="exclude">Exclude</Option>
@@ -142,12 +156,16 @@ const ProjectForm = ({ fields, onChange }: WizardComponentProps) => {
142156
size="default"
143157
clearable={true}
144158
multiselect={true}
145-
value={projectFields}
146-
onChange={onSelectField}
159+
value={projectFormState.projectionFields}
160+
onChange={(fields) =>
161+
handleProjectFormStateChange('projectionFields', [...fields])
162+
}
147163
options={fieldNames}
148164
optionLabel="Field:"
149165
overflow="scroll-x"
150-
isOptionDisabled={makeIsOptionDisabled(projectFields)}
166+
isOptionDisabled={makeIsOptionDisabled(
167+
projectFormState.projectionFields
168+
)}
151169
// Used for testing to access the popover for a stage
152170
popoverClassName="project-form-field-combobox"
153171
/>

0 commit comments

Comments
 (0)