Skip to content

Commit dc1fa2d

Browse files
authored
Merge pull request #1362 from rvsia/splitUpSelect
feat(common): separate functionality from common select
2 parents 5b5b337 + f58a9b2 commit dc1fa2d

File tree

9 files changed

+267
-187
lines changed

9 files changed

+267
-187
lines changed

packages/common/src/select/select.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AnyObject } from "@data-driven-forms/react-form-renderer";
22

3-
type option = {
3+
export type option = {
44
[key: string]: any;
55
label?: React.ReactNode;
66
value?: any;

packages/common/src/select/select.js

Lines changed: 24 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,9 @@
11
/* eslint-disable react-hooks/exhaustive-deps */
2-
import React, { useEffect, useReducer } from 'react';
2+
import React from 'react';
33

44
import PropTypes from 'prop-types';
55
import clsx from 'clsx';
6-
import isEqual from 'lodash/isEqual';
7-
import fnToString from '../utils/fn-to-string';
8-
import reducer, { init } from './reducer';
9-
import useIsMounted from '../hooks/use-is-mounted';
10-
11-
const getSelectValue = (stateValue, simpleValue, isMulti, allOptions) => {
12-
let enhancedValue = stateValue;
13-
14-
let hasSelectAll = isMulti && allOptions.find(({ selectAll }) => selectAll);
15-
let hasSelectNone = isMulti && allOptions.find(({ selectNone }) => selectNone);
16-
17-
if (hasSelectAll || hasSelectNone) {
18-
enhancedValue = enhancedValue || [];
19-
const optionsLength = allOptions.filter(
20-
({ selectAll, selectNone, divider, options }) => !selectAll && !selectNone && !divider && !options
21-
).length;
22-
23-
const selectedAll = optionsLength === enhancedValue.length;
24-
const selectedNone = enhancedValue.length === 0;
25-
26-
enhancedValue = [
27-
...enhancedValue,
28-
...(hasSelectAll && selectedAll ? [simpleValue ? hasSelectAll.value : hasSelectAll] : []),
29-
...(hasSelectNone && selectedNone ? [simpleValue ? hasSelectNone.value : hasSelectNone] : []),
30-
];
31-
}
32-
33-
return simpleValue ? allOptions.filter(({ value }) => (isMulti ? enhancedValue.includes(value) : isEqual(value, enhancedValue))) : enhancedValue;
34-
};
35-
36-
const handleSelectChange = (option, simpleValue, isMulti, onChange, allOptions, removeSelectAll, removeSelectNone) => {
37-
let enhanceOption = option;
38-
39-
if (removeSelectNone) {
40-
enhanceOption = enhanceOption.filter(({ selectNone }) => !selectNone);
41-
} else if (removeSelectAll) {
42-
enhanceOption = enhanceOption.filter(({ selectAll }) => !selectAll);
43-
}
44-
45-
const sanitizedOption = !enhanceOption && isMulti ? [] : enhanceOption;
46-
47-
if (isMulti && sanitizedOption.find(({ selectAll }) => selectAll)) {
48-
return onChange(allOptions.filter(({ selectAll, selectNone, value }) => !selectAll && !selectNone && value).map(({ value }) => value));
49-
}
50-
51-
if (isMulti && sanitizedOption.find(({ selectNone }) => selectNone)) {
52-
return onChange([]);
53-
}
54-
55-
return simpleValue
56-
? onChange(isMulti ? sanitizedOption.map((item) => item.value) : sanitizedOption ? sanitizedOption.value : undefined)
57-
: onChange(sanitizedOption);
58-
};
6+
import useSelect from '../use-select/use-select';
597

608
const Select = ({
619
invalid,
@@ -78,57 +26,25 @@ const Select = ({
7826
optionsTransformer,
7927
...props
8028
}) => {
81-
const [state, originalDispatch] = useReducer(reducer, { optionsTransformer, propsOptions }, init);
82-
const dispatch = (action) => originalDispatch({ ...action, optionsTransformer });
83-
84-
const isMounted = useIsMounted();
85-
86-
const updateOptions = () => {
87-
dispatch({ type: 'startLoading' });
88-
89-
return loadOptions().then((data) => {
90-
if (isMounted.current) {
91-
if (!noValueUpdates) {
92-
if (value && Array.isArray(value)) {
93-
const selectValue = value.filter((value) =>
94-
typeof value === 'object' ? data.find((option) => value.value === option.value) : data.find((option) => value === option.value)
95-
);
96-
onChange(selectValue.length === 0 ? undefined : selectValue);
97-
} else if (value && !data.find(({ value: internalValue }) => internalValue === value)) {
98-
onChange(undefined);
99-
}
100-
}
101-
102-
dispatch({ type: 'updateOptions', payload: data });
103-
}
104-
});
105-
};
106-
107-
useEffect(() => {
108-
if (loadOptions) {
109-
updateOptions();
110-
}
111-
112-
dispatch({ type: 'initialLoaded' });
113-
}, []);
114-
115-
const loadOptionsStr = loadOptions ? fnToString(loadOptions) : '';
116-
117-
useEffect(() => {
118-
if (loadOptionsStr && state.isInitialLoaded) {
119-
updateOptions();
120-
}
121-
}, [loadOptionsStr, loadOptionsChangeCounter]);
122-
123-
useEffect(() => {
124-
if (state.isInitialLoaded) {
125-
if (!noValueUpdates && value && !propsOptions.map(({ value }) => value).includes(value)) {
126-
onChange(undefined);
127-
}
128-
129-
dispatch({ type: 'setOptions', payload: propsOptions });
130-
}
131-
}, [propsOptions]);
29+
const {
30+
state,
31+
value: selectValue,
32+
onChange: selectOnChange,
33+
onInputChange,
34+
isFetching,
35+
} = useSelect({
36+
loadOptions,
37+
optionsTransformer,
38+
options: propsOptions,
39+
noValueUpdates,
40+
onChange,
41+
value,
42+
loadOptionsChangeCounter,
43+
isSearchable: props.isSearchable,
44+
pluckSingleValue,
45+
isMulti,
46+
simpleValue,
47+
});
13248

13349
const renderNoOptionsMessage = () => (Object.values(state.promises).some((value) => value) ? () => updatingMessage : () => noOptionsMessage);
13450

@@ -149,34 +65,6 @@ const Select = ({
14965
);
15066
}
15167

152-
const onInputChange = (inputValue) => {
153-
if (inputValue && loadOptions && state.promises[inputValue] === undefined && props.isSearchable) {
154-
dispatch({ type: 'setPromises', payload: { [inputValue]: true } });
155-
156-
loadOptions(inputValue)
157-
.then((options) => {
158-
if (isMounted.current) {
159-
dispatch({
160-
type: 'setPromises',
161-
payload: { [inputValue]: false },
162-
options,
163-
});
164-
}
165-
})
166-
.catch((error) => {
167-
dispatch({ type: 'setPromises', payload: { [inputValue]: false } });
168-
// eslint-disable-next-line no-console
169-
console.error(error);
170-
});
171-
}
172-
};
173-
174-
const selectValue = pluckSingleValue ? (isMulti ? value : Array.isArray(value) && value[0] ? value[0] : value) : value;
175-
176-
const filteredLength = state.options.filter(({ selectAll, selectNone }) => !selectAll && !selectNone).length;
177-
const shouldRemoveSelectAll = isMulti && state.options.find(({ selectAll }) => selectAll) && selectValue.length === filteredLength;
178-
const shouldRemoveSelectNone = isMulti && state.options.find(({ selectNone }) => selectNone) && selectValue.length === 0;
179-
18068
return (
18169
<SelectComponent
18270
className={clsx(classNamePrefix, {
@@ -187,10 +75,10 @@ const Select = ({
18775
options={state.options}
18876
classNamePrefix={classNamePrefix}
18977
isMulti={isMulti}
190-
value={getSelectValue(selectValue, simpleValue, isMulti, state.options)}
191-
onChange={(option) => handleSelectChange(option, simpleValue, isMulti, onChange, state.options, shouldRemoveSelectAll, shouldRemoveSelectNone)}
78+
value={selectValue}
79+
onChange={selectOnChange}
19280
onInputChange={onInputChange}
193-
isFetching={Object.values(state.promises).some((value) => value)}
81+
isFetching={isFetching}
19482
noOptionsMessage={renderNoOptionsMessage()}
19583
hideSelectedOptions={false}
19684
closeMenuOnSelect={!isMulti}

packages/common/src/tests/select/select.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import userEvent from '@testing-library/user-event';
77
import { useFieldApi, FormRenderer, componentTypes } from '@data-driven-forms/react-form-renderer';
88

99
import Select from '../../select';
10-
import reducer from '../../select/reducer';
10+
import reducer from '../../use-select/reducer';
1111

1212
describe('Select test', () => {
1313
let state;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default } from './use-select';
2+
export * from './use-select';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default } from './use-select';
2+
export * from './use-select';
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { AnyObject } from '@data-driven-forms/react-form-renderer';
2+
import { option } from '../select';
3+
4+
interface UseSelectProps {
5+
loadOptions?: (inputValue?: string) => Promise<option[]>;
6+
optionsTransformer?: (options: AnyObject[]) => option[];
7+
options?: option[];
8+
noValueUpdates?: boolean;
9+
onChange?: (value?: any) => void;
10+
value?: any;
11+
loadOptionsChangeCounter?: number;
12+
isSearchable?: boolean;
13+
pluckSingleValue?: boolean;
14+
isMulti?: boolean;
15+
simpleValue?: boolean;
16+
}
17+
18+
interface SelectState {
19+
isLoading: boolean;
20+
options: option[];
21+
promises: AnyObject;
22+
isInitialLoaded: boolean;
23+
originalOptions?: option[];
24+
}
25+
26+
declare function useSelect(props: UseSelectProps): {
27+
value: any;
28+
onChange: (option: option) => void;
29+
onInputChange: (inputValue: string) => void;
30+
isFetching: boolean;
31+
state: SelectState;
32+
}
33+
34+
export default useSelect;

0 commit comments

Comments
 (0)