Skip to content

Commit aaa1b8a

Browse files
committed
chore: optimize selects with search Ref: LINK-1759
1 parent b93fa18 commit aaa1b8a

7 files changed

Lines changed: 223 additions & 83 deletions

File tree

src/common/components/eventSelector/EventSelector.tsx

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable no-undef */
22
import { ApolloError } from '@apollo/client';
33
import { SearchFunction, SearchResult } from 'hds-react';
4-
import React, { useCallback } from 'react';
4+
import React, { useCallback, useEffect } from 'react';
55
import { useTranslation } from 'react-i18next';
66

77
import { eventPathBuilder } from '../../../domain/event/utils';
@@ -30,11 +30,15 @@ const EventSelector: React.FC<EventSelectorProps> = ({
3030
name,
3131
value,
3232
variables,
33+
onChange,
3334
...rest
3435
}) => {
3536
const { t } = useTranslation();
3637
const locale = useLocale();
3738

39+
const [options, setOptions] = React.useState<OptionType[]>([]);
40+
const initialOptions = React.useRef<OptionType[]>([]);
41+
3842
const {
3943
data: eventsData,
4044
loading,
@@ -66,25 +70,53 @@ const EventSelector: React.FC<EventSelectorProps> = ({
6670
[eventsData, getOption, locale, previousEventsData]
6771
);
6872

69-
const options = React.useMemo(() => getEventsData(), [getEventsData]);
73+
const eventsOptions = React.useMemo(() => getEventsData(), [getEventsData]);
74+
75+
useEffect(() => {
76+
setOptions(eventsOptions);
77+
78+
if (eventsData && !initialOptions?.current.length) {
79+
initialOptions.current = eventsOptions;
80+
}
81+
}, [eventsOptions, eventsData]);
7082

71-
const handleSearch: SearchFunction = async (
72-
searchValue: string
73-
): Promise<SearchResult> => {
74-
try {
75-
const { error } = await refetch({
76-
text: searchValue,
77-
});
83+
const handleSearch: SearchFunction = React.useCallback(
84+
async (searchValue: string): Promise<SearchResult> => {
85+
try {
86+
const { error, data: newEventsData } = await refetch({
87+
text: searchValue,
88+
});
7889

79-
if (error) {
80-
throw error;
90+
if (error) {
91+
throw error;
92+
}
93+
94+
const newOptions = getValue(
95+
newEventsData?.events.data.map((event) =>
96+
getOption(event as EventFieldsFragment, locale)
97+
),
98+
[]
99+
);
100+
101+
return { options: newOptions };
102+
} catch (error) {
103+
return Promise.reject(error as ApolloError);
81104
}
105+
},
106+
[refetch, getOption, locale]
107+
);
82108

83-
return { options: getEventsData() };
84-
} catch (error) {
85-
return Promise.reject(error as ApolloError);
86-
}
87-
};
109+
const handleChange = React.useCallback(
110+
(...args: unknown[]) => {
111+
setOptions(initialOptions.current);
112+
113+
if (onChange) {
114+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
115+
(onChange as any)(...args);
116+
}
117+
},
118+
[onChange]
119+
);
88120

89121
const selectedEvent = React.useMemo(
90122
() => (eventData?.event ? getOption(eventData.event, locale) : null),
@@ -94,7 +126,9 @@ const EventSelector: React.FC<EventSelectorProps> = ({
94126
return (
95127
<Select
96128
{...rest}
129+
multiSelect={false}
97130
onSearch={handleSearch}
131+
onChange={handleChange}
98132
id={name}
99133
isLoading={loading}
100134
texts={{

src/common/components/formFields/keywordSelectorField/KeywordSelectorField.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const KeywordSelectorField: React.FC<Props> = ({
2828
disabled={disabled}
2929
name={name}
3030
onBlur={handleBlur}
31-
onClose={handleClose}
31+
handleClose={handleClose}
3232
value={value}
3333
texts={{ ...texts, error: errorText }}
3434
invalid={!!errorText}

src/common/components/keywordSelector/KeywordSelector.tsx

Lines changed: 69 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
NormalizedCacheObject,
66
useApolloClient,
77
} from '@apollo/client';
8-
import { SearchFunction, SearchResult } from 'hds-react';
9-
import React from 'react';
8+
import { Option, SearchFunction, SearchResult, SelectData } from 'hds-react';
9+
import React, { useEffect } from 'react';
1010
import { useTranslation } from 'react-i18next';
1111

1212
import {
@@ -39,12 +39,15 @@ const getOption = ({
3939
return { label, value };
4040
};
4141

42-
export type KeywordSelectorProps = MultiSelectPropsWithValue<string>;
42+
export type KeywordSelectorProps = MultiSelectPropsWithValue<string> & {
43+
handleClose: (selectedOptions: OptionType[]) => void;
44+
};
4345

4446
const KeywordSelector: React.FC<KeywordSelectorProps> = ({
4547
texts,
4648
name,
4749
value,
50+
handleClose,
4851
...rest
4952
}) => {
5053
const apolloClient = useApolloClient() as ApolloClient<NormalizedCacheObject>;
@@ -54,6 +57,9 @@ const KeywordSelector: React.FC<KeywordSelectorProps> = ({
5457
const [selectedKeywords, setSelectedKeywords] = React.useState<OptionType[]>(
5558
[]
5659
);
60+
const [options, setOptions] = React.useState<OptionType[]>([]);
61+
62+
const initialOptions = React.useRef<OptionType[]>([]);
5763

5864
const {
5965
data: keywordsData,
@@ -69,7 +75,7 @@ const KeywordSelector: React.FC<KeywordSelectorProps> = ({
6975
},
7076
});
7177

72-
const getKeywordsData = React.useCallback(
78+
const keywordsOptions = React.useMemo(
7379
() =>
7480
getValue(
7581
(keywordsData || previousKeywordsData)?.keywords.data.map((keyword) =>
@@ -80,43 +86,71 @@ const KeywordSelector: React.FC<KeywordSelectorProps> = ({
8086
[keywordsData, locale, previousKeywordsData]
8187
);
8288

83-
const options = React.useMemo(() => getKeywordsData(), [getKeywordsData]);
84-
85-
const handleSearch: SearchFunction = async (
86-
searchValue: string
87-
): Promise<SearchResult> => {
88-
try {
89-
const { error } = await refetch({
90-
freeText: searchValue,
91-
});
89+
useEffect(() => {
90+
setOptions(keywordsOptions);
9291

93-
if (error) {
94-
throw error;
92+
if (keywordsData && !initialOptions?.current.length) {
93+
initialOptions.current = keywordsOptions;
94+
}
95+
}, [keywordsOptions, keywordsData]);
96+
97+
const handleSearch: SearchFunction = React.useCallback(
98+
async (searchValue: string): Promise<SearchResult> => {
99+
try {
100+
const { error, data: newKeywordsData } = await refetch({
101+
freeText: searchValue,
102+
});
103+
104+
if (error) {
105+
throw error;
106+
}
107+
108+
const newOptions = getValue(
109+
newKeywordsData?.keywords.data.map((keyword) =>
110+
getOption({ keyword: keyword as KeywordFieldsFragment, locale })
111+
),
112+
[]
113+
);
114+
115+
return Promise.resolve({ options: newOptions });
116+
} catch (error) {
117+
return Promise.reject(error as ApolloError);
95118
}
119+
},
120+
[refetch, locale]
121+
);
96122

97-
return Promise.resolve({ options: getKeywordsData() });
98-
} catch (error) {
99-
return Promise.reject(error as ApolloError);
100-
}
101-
};
123+
const onClose = React.useCallback(
124+
(
125+
selectedOptions: Option[],
126+
_clickedOption: undefined,
127+
_data: SelectData
128+
) => {
129+
setOptions(initialOptions.current);
130+
131+
if (handleClose) {
132+
handleClose(selectedOptions);
133+
}
134+
},
135+
[handleClose]
136+
);
102137

103138
React.useEffect(() => {
104-
const getSelectedKeywordsFromCache = async () =>
105-
setSelectedKeywords(
106-
(
107-
await Promise.all(
108-
value.map(async (atId) => {
109-
const keyword = await getKeywordQueryResult(
110-
getValue(parseIdFromAtId(atId), ''),
111-
apolloClient
112-
);
113-
/* istanbul ignore next */
114-
return keyword ? getOption({ keyword: keyword, locale }) : null;
115-
})
116-
)
117-
).filter(skipFalsyType)
139+
const getSelectedKeywordsFromCache = async () => {
140+
const selectedOptions = await Promise.all(
141+
value.map(async (atId) => {
142+
const keyword = await getKeywordQueryResult(
143+
getValue(parseIdFromAtId(atId), ''),
144+
apolloClient
145+
);
146+
/* istanbul ignore next */
147+
return keyword ? getOption({ keyword: keyword, locale }) : null;
148+
})
118149
);
119150

151+
setSelectedKeywords(selectedOptions.filter(skipFalsyType));
152+
};
153+
120154
getSelectedKeywordsFromCache();
121155
}, [apolloClient, locale, value]);
122156

@@ -125,6 +159,7 @@ const KeywordSelector: React.FC<KeywordSelectorProps> = ({
125159
{...rest}
126160
multiSelect
127161
onSearch={handleSearch}
162+
onClose={onClose}
128163
id={name}
129164
isLoading={loading}
130165
texts={{

src/common/components/keywordSelector/__tests__/KeywordSelector.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const defaultProps: KeywordSelectorProps = {
4343
name,
4444
value: [keywordAtId],
4545
onChange: vi.fn(),
46+
handleClose: vi.fn(),
4647
};
4748

4849
const renderComponent = (props?: Partial<KeywordSelectorProps>) =>

src/common/components/placeSelector/PlaceSelector.tsx

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { ApolloError } from '@apollo/client';
33
import { SearchFunction, SearchResult } from 'hds-react';
44
import { TFunction } from 'i18next';
5-
import React, { useCallback } from 'react';
5+
import React, { useCallback, useEffect } from 'react';
66
import { useTranslation } from 'react-i18next';
77

88
import {
@@ -63,11 +63,15 @@ const PlaceSelector: React.FC<PlaceSelectorProps> = ({
6363
texts,
6464
value,
6565
name,
66+
onChange,
6667
...rest
6768
}) => {
6869
const { t } = useTranslation();
6970
const locale = useLocale();
7071

72+
const [options, setOptions] = React.useState<OptionType[]>([]);
73+
const initialOptions = React.useRef<OptionType[]>([]);
74+
7175
const {
7276
data: placesData,
7377
loading,
@@ -100,25 +104,53 @@ const PlaceSelector: React.FC<PlaceSelectorProps> = ({
100104
[locale, placesData, previousPlacesData, t]
101105
);
102106

103-
const options = React.useMemo(() => getPlacesData(), [getPlacesData]);
107+
const placesOptions = React.useMemo(() => getPlacesData(), [getPlacesData]);
104108

105-
const handleSearch: SearchFunction = async (
106-
searchValue: string
107-
): Promise<SearchResult> => {
108-
try {
109-
const { error } = await refetch({
110-
text: searchValue,
111-
});
109+
useEffect(() => {
110+
setOptions(placesOptions);
112111

113-
if (error) {
114-
throw error;
112+
if (placesData && !initialOptions?.current.length) {
113+
initialOptions.current = placesOptions;
114+
}
115+
}, [placesOptions, placesData]);
116+
117+
const handleSearch: SearchFunction = React.useCallback(
118+
async (searchValue: string): Promise<SearchResult> => {
119+
try {
120+
const { error, data: newPlacesData } = await refetch({
121+
text: searchValue,
122+
});
123+
124+
if (error) {
125+
throw error;
126+
}
127+
128+
const newOptions = getValue(
129+
newPlacesData?.places.data.map((place) =>
130+
getOption({ place: place as PlaceFieldsFragment, locale, t })
131+
),
132+
[]
133+
);
134+
135+
return { options: newOptions };
136+
} catch (error) {
137+
return Promise.reject(error as ApolloError);
115138
}
139+
},
140+
[refetch, locale, t]
141+
);
116142

117-
return { options: getPlacesData() };
118-
} catch (error) {
119-
return Promise.reject(error as ApolloError);
120-
}
121-
};
143+
const handleChange = React.useCallback(
144+
(...args: unknown[]) => {
145+
setOptions(initialOptions.current);
146+
147+
if (onChange) {
148+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
149+
(onChange as any)(...args);
150+
}
151+
},
152+
[onChange]
153+
);
122154

123155
const selectedPlace = React.useMemo(
124156
() =>
@@ -142,12 +174,13 @@ const PlaceSelector: React.FC<PlaceSelectorProps> = ({
142174
id={name}
143175
isLoading={loading}
144176
onSearch={handleSearch}
177+
onChange={handleChange}
145178
options={options}
146179
texts={{
147180
...texts,
148181
clearButtonAriaLabel_one: t('common.combobox.clearPlaces'),
149182
}}
150-
value={selectedPlace?.value}
183+
value={selectedPlace?.label}
151184
/>
152185
);
153186
};

src/common/components/publisherSelector/PublisherSelector.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const PublisherSelector: React.FC<PublisherSelectorProps> = ({
6969
return (
7070
<Select
7171
{...rest}
72+
multiSelect={false}
7273
clearable={clearable}
7374
id={name}
7475
isLoading={loading}

0 commit comments

Comments
 (0)