diff --git a/src/generate.tsx b/src/generate.tsx index be4c9d420..e4c172409 100644 --- a/src/generate.tsx +++ b/src/generate.tsx @@ -187,10 +187,15 @@ export interface GenerateConfig { /** Convert single raw value into { label, value } format. Will be called by each value */ getLabeledValue: GetLabeledValue>; filterOptions: FilterOptions; - findValueOption: ( - values: RawValueType[], - options: FlattenOptionsType, - ) => OptionsType; + findValueOption: + | (// Need still support legacy ts api + (values: RawValueType[], options: FlattenOptionsType) => OptionsType) + | (// New API add prevValueOptions support + ( + values: RawValueType[], + options: FlattenOptionsType, + info?: { prevValueOptions?: OptionsType[] }, + ) => OptionsType); /** Check if a value is disabled */ isValueDisabled: (value: RawValueType, options: FlattenOptionsType) => boolean; warningProps?: (props: any) => void; @@ -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; @@ -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]); } diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts index c6cd8e01a..ff60640a3 100644 --- a/src/utils/valueUtil.ts +++ b/src/utils/valueUtil.ts @@ -90,6 +90,7 @@ function injectPropsWithOption(option: T): T { export function findValueOption( values: RawValueType[], options: FlattenOptionData[], + { prevValueOptions = [] }: { prevValueOptions?: OptionData[] } = {}, ): OptionData[] { const optionMap: Map = new Map(); @@ -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 = ( diff --git a/tests/Multiple.test.tsx b/tests/Multiple.test.tsx index bcb014495..f14fd5b16 100644 --- a/tests/Multiple.test.tsx +++ b/tests/Multiple.test.tsx @@ -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( @@ -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( +