Skip to content

Commit

Permalink
Merge pull request JedWatson#4444 from Rall3n/compare-option-accessors
Browse files Browse the repository at this point in the history
Use accessor props to get value and label in `compareOption`
  • Loading branch information
JedWatson committed Feb 18, 2021
2 parents 2962385 + 2ffed9c commit 88cb46a
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/bright-tigers-tie.md
@@ -0,0 +1,5 @@
---
'react-select': minor
---

Use accessor props to get value and label in `compareOption`
38 changes: 30 additions & 8 deletions packages/react-select/src/Creatable.js
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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;
};

Expand All @@ -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 = {
Expand Down Expand Up @@ -109,10 +124,17 @@ export const makeCreatableSelect = <C: {}>(
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;
Expand Down
48 changes: 48 additions & 0 deletions packages/react-select/src/__tests__/Creatable.test.js
Expand Up @@ -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(
<Creatable
menuIsOpen
getOptionLabel={getOptionLabel}
getOptionValue={getOptionValue}
{...props}
/>
);

rerender(
<Creatable
menuIsOpen
getOptionLabel={getOptionLabel}
getOptionValue={getOptionValue}
inputValue="testc"
{...props}
/>
);
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,
},
},
}
);

0 comments on commit 88cb46a

Please sign in to comment.