Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions src/generate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,15 @@ export interface GenerateConfig<OptionsType extends object[]> {
/** Convert single raw value into { label, value } format. Will be called by each value */
getLabeledValue: GetLabeledValue<FlattenOptionsType<OptionsType>>;
filterOptions: FilterOptions<OptionsType>;
findValueOption: (
values: RawValueType[],
options: FlattenOptionsType<OptionsType>,
) => OptionsType;
findValueOption:
| (// Need still support legacy ts api
(values: RawValueType[], options: FlattenOptionsType<OptionsType>) => OptionsType)
| (// New API add prevValueOptions support
(
values: RawValueType[],
options: FlattenOptionsType<OptionsType>,
info?: { prevValueOptions?: OptionsType[] },
) => OptionsType);
/** Check if a value is disabled */
isValueDisabled: (value: RawValueType, options: FlattenOptionsType<OptionsType>) => boolean;
warningProps?: (props: any) => void;
Expand Down Expand Up @@ -515,6 +520,9 @@ export default function generateSelector<
}
};

// We need cache options here in case user update the option list
const [prevValueOptions, setPrevValueOptions] = useState([]);

const triggerChange = (newRawValues: RawValueType[]) => {
if (useInternalProps && internalProps.skipTriggerChange) {
return;
Expand All @@ -531,7 +539,18 @@ export default function generateSelector<
const outValue: ValueType = (isMultiple ? outValues : outValues[0]) as ValueType;
// Skip trigger if prev & current value is both empty
if (onChange && (mergedRawValue.length !== 0 || outValues.length !== 0)) {
const outOptions = findValueOption(newRawValues, newRawValuesOptions);
const outOptions = findValueOption(newRawValues, newRawValuesOptions, { prevValueOptions });

// We will cache option in case it removed by ajax
setPrevValueOptions(
outOptions.map((option, index) => {
const clone = { ...option };
Object.defineProperty(clone, '_INTERNAL_OPTION_VALUE_', {
get: () => newRawValues[index],
});
return clone;
}),
);

onChange(outValue, isMultiple ? outOptions : outOptions[0]);
}
Expand Down
15 changes: 14 additions & 1 deletion src/utils/valueUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ function injectPropsWithOption<T>(option: T): T {
export function findValueOption(
values: RawValueType[],
options: FlattenOptionData[],
{ prevValueOptions = [] }: { prevValueOptions?: OptionData[] } = {},
): OptionData[] {
const optionMap: Map<RawValueType, OptionData> = new Map();

Expand All @@ -101,7 +102,19 @@ export function findValueOption(
}
});

return values.map(val => injectPropsWithOption(optionMap.get(val)));
return values.map(val => {
let option = optionMap.get(val);

// Fallback to try to find prev options
if (!option) {
option = {
// eslint-disable-next-line no-underscore-dangle
...prevValueOptions.find(opt => opt._INTERNAL_OPTION_VALUE_ === val),
};
}

return injectPropsWithOption(option);
});
}

export const getLabeledValue: GetLabeledValue<FlattenOptionData[]> = (
Expand Down
40 changes: 39 additions & 1 deletion tests/Multiple.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ describe('Select.Multiple', () => {
expect(wrapper.find('input').props().value).toBe('');
});

it('shouldn\'t separate words when compositing', () => {
it("shouldn't separate words when compositing", () => {
const handleChange = jest.fn();
const handleSelect = jest.fn();
const wrapper = mount(
Expand Down Expand Up @@ -391,4 +391,42 @@ describe('Select.Multiple', () => {
toggleOpen(wrapper);
expect(wrapper.find('input').props().value).toEqual('');
});

it('ajax update should keep options', () => {
const onChange = jest.fn();

const wrapper = mount(
<Select
labelInValue
mode="multiple"
options={[{ value: 'light', label: 'Light', option: 2333 }]}
onChange={onChange}
showSearch
/>,
);

// First select
toggleOpen(wrapper);
selectItem(wrapper, 0);
expect(onChange).toHaveBeenCalledWith(
[{ label: 'Light', value: 'light', key: 'light' }],
[{ label: 'Light', value: 'light', option: 2333 }],
);
onChange.mockReset();

// Next select
wrapper.setProps({ options: [{ value: 'bamboo', label: 'Bamboo', option: 444 }] });
toggleOpen(wrapper);
selectItem(wrapper, 0);
expect(onChange).toHaveBeenCalledWith(
[
{ label: 'Light', value: 'light', key: 'light' },
{ label: 'Bamboo', value: 'bamboo', key: 'bamboo' },
],
[
{ label: 'Light', value: 'light', option: 2333 },
{ value: 'bamboo', label: 'Bamboo', option: 444 },
],
);
});
});