From 8ae245b72f5e36535f16f2822fb76f631130c40b Mon Sep 17 00:00:00 2001 From: Rallen Date: Tue, 9 Feb 2021 23:53:46 +0100 Subject: [PATCH] Use accessor props to get value and label in `compareOption` --- packages/react-select/src/Creatable.js | 38 +++++++++++---- .../src/__tests__/Creatable.test.js | 48 +++++++++++++++++++ 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/packages/react-select/src/Creatable.js b/packages/react-select/src/Creatable.js index a30069b30d..77dc362985 100644 --- a/packages/react-select/src/Creatable.js +++ b/packages/react-select/src/Creatable.js @@ -12,6 +12,15 @@ import Select, { type Props as SelectProps } from './Select'; import type { OptionType, OptionsType, ValueType, ActionMeta } from './types'; import { cleanValue } from './utils'; import manageState from './stateManager'; +import { + getOptionValue as baseGetOptionValue, + getOptionLabel as baseGetOptionLabel, +} from './builtins'; + +type AccessorType = {| + getOptionValue: typeof baseGetOptionValue, + getOptionLabel: typeof baseGetOptionLabel, +|}; export type DefaultCreatableProps = {| /* Allow options to be created while the `isLoading` prop is true. Useful to @@ -25,10 +34,11 @@ export type DefaultCreatableProps = {| formatCreateLabel: string => Node, /* Determines whether the "create new ..." option should be displayed based on the current input value, select value and options array. */ - isValidNewOption: (string, OptionsType, OptionsType) => boolean, + isValidNewOption: (string, OptionsType, OptionsType, AccessorType) => boolean, /* Returns the data for the new option when it is created. Used to display the value, and is passed to `onChange`. */ getNewOptionData: (string, Node) => OptionType, + ...AccessorType, |}; export type CreatableProps = { ...DefaultCreatableProps, @@ -48,10 +58,10 @@ export type CreatableProps = { export type Props = SelectProps & CreatableProps; -const compareOption = (inputValue = '', option) => { +const compareOption = (inputValue = '', option, accessors) => { const candidate = String(inputValue).toLowerCase(); - const optionValue = String(option.value).toLowerCase(); - const optionLabel = String(option.label).toLowerCase(); + const optionValue = String(accessors.getOptionValue(option)).toLowerCase(); + const optionLabel = String(accessors.getOptionLabel(option)).toLowerCase(); return optionValue === candidate || optionLabel === candidate; }; @@ -60,18 +70,23 @@ const builtins = { isValidNewOption: ( inputValue: string, selectValue: OptionsType, - selectOptions: OptionsType + selectOptions: OptionsType, + accessors: AccessorType ) => !( !inputValue || - selectValue.some(option => compareOption(inputValue, option)) || - selectOptions.some(option => compareOption(inputValue, option)) + selectValue.some(option => + compareOption(inputValue, option, accessors) + ) || + selectOptions.some(option => compareOption(inputValue, option, accessors)) ), getNewOptionData: (inputValue: string, optionLabel: Node) => ({ label: optionLabel, value: inputValue, __isNew__: true, }), + getOptionValue: baseGetOptionValue, + getOptionLabel: baseGetOptionLabel, }; export const defaultProps: DefaultCreatableProps = { @@ -109,10 +124,17 @@ export const makeCreatableSelect = ( isLoading, isValidNewOption, value, + getOptionValue, + getOptionLabel, } = props; const options = props.options || []; let { newOption } = state; - if (isValidNewOption(inputValue, cleanValue(value), options)) { + if ( + isValidNewOption(inputValue, cleanValue(value), options, { + getOptionValue, + getOptionLabel, + }) + ) { newOption = getNewOptionData(inputValue, formatCreateLabel(inputValue)); } else { newOption = undefined; diff --git a/packages/react-select/src/__tests__/Creatable.test.js b/packages/react-select/src/__tests__/Creatable.test.js index ce41c860f2..4c06ae2c8f 100644 --- a/packages/react-select/src/__tests__/Creatable.test.js +++ b/packages/react-select/src/__tests__/Creatable.test.js @@ -254,3 +254,51 @@ cases( }, } ); + +const CUSTOM_OPTIONS = [ + { key: 'testa', title: 'Test A' }, + { key: 'testb', title: 'Test B' }, + { key: 'testc', title: 'Test C' }, + { key: 'testd', title: 'Test D' }, +]; + +cases( + 'compareOption() method', + ({ props = { options: CUSTOM_OPTIONS } }) => { + props = { ...BASIC_PROPS, ...props }; + + const getOptionLabel = ({ title }) => title; + const getOptionValue = ({ key }) => key; + + const { container, rerender } = render( + + ); + + rerender( + + ); + expect(container.querySelector('.react-select__menu').textContent).toEqual( + 'Test C' + ); + }, + { + 'single select > should handle options with custom structure': {}, + 'single select > should handle options with custom structure': { + props: { + isMulti: true, + options: CUSTOM_OPTIONS, + }, + }, + } +);