• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JedWatson / react-select / 36c10f1a-e614-479e-8065-a6ee4ffe0d2e

11 Dec 2024 10:57PM CUT coverage: 75.844%. Remained the same
36c10f1a-e614-479e-8065-a6ee4ffe0d2e

Pull #5984

circleci

web-flow
Create five-ligers-beg.md
Pull Request #5984: Add peer version to include 19

658 of 1052 branches covered (62.55%)

1033 of 1362 relevant lines covered (75.84%)

1934.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.71
/packages/react-select/src/useCreatable.ts
1
import { ReactNode, useCallback, useMemo } from 'react';
2
import { PublicBaseSelectProps } from './Select';
3
import {
4
  ActionMeta,
5
  GetOptionLabel,
6
  GetOptionValue,
7
  GroupBase,
8
  OnChangeValue,
9
  Options,
10
  OptionsOrGroups,
11
} from './types';
12
import { cleanValue, valueTernary } from './utils';
13
import {
14
  getOptionValue as baseGetOptionValue,
15
  getOptionLabel as baseGetOptionLabel,
16
} from './builtins';
17

18
export interface Accessors<Option> {
19
  getOptionValue: GetOptionValue<Option>;
20
  getOptionLabel: GetOptionLabel<Option>;
21
}
22

23
export interface CreatableAdditionalProps<
24
  Option,
25
  Group extends GroupBase<Option>
26
> {
27
  /**
28
   * Allow options to be created while the `isLoading` prop is true. Useful to
29
   * prevent the "create new ..." option being displayed while async results are
30
   * still being loaded.
31
   */
32
  allowCreateWhileLoading?: boolean;
33
  /** Sets the position of the createOption element in your options list. Defaults to 'last' */
34
  createOptionPosition?: 'first' | 'last';
35
  /**
36
   * Gets the label for the "create new ..." option in the menu. Is given the
37
   * current input value.
38
   */
39
  formatCreateLabel?: (inputValue: string) => ReactNode;
40
  /**
41
   * Determines whether the "create new ..." option should be displayed based on
42
   * the current input value, select value and options array.
43
   */
44
  isValidNewOption?: (
45
    inputValue: string,
46
    value: Options<Option>,
47
    options: OptionsOrGroups<Option, Group>,
48
    accessors: Accessors<Option>
49
  ) => boolean;
50
  /**
51
   * Returns the data for the new option when it is created. Used to display the
52
   * value, and is passed to `onChange`.
53
   */
54
  getNewOptionData?: (inputValue: string, optionLabel: ReactNode) => Option;
55
  /**
56
   * If provided, this will be called with the input value when a new option is
57
   * created, and `onChange` will **not** be called. Use this when you need more
58
   * control over what happens when new options are created.
59
   */
60
  onCreateOption?: (inputValue: string) => void;
61
}
62

63
type BaseCreatableProps<
64
  Option,
65
  IsMulti extends boolean,
66
  Group extends GroupBase<Option>
67
> = PublicBaseSelectProps<Option, IsMulti, Group> &
68
  CreatableAdditionalProps<Option, Group>;
69

70
const compareOption = <Option>(
2✔
71
  inputValue = '',
×
72
  option: Option,
73
  accessors: Accessors<Option>
74
) => {
75
  const candidate = String(inputValue).toLowerCase();
184✔
76
  const optionValue = String(accessors.getOptionValue(option)).toLowerCase();
184✔
77
  const optionLabel = String(accessors.getOptionLabel(option)).toLowerCase();
184✔
78
  return optionValue === candidate || optionLabel === candidate;
184✔
79
};
80

81
const builtins = {
2✔
82
  formatCreateLabel: (inputValue: string) => `Create "${inputValue}"`,
16✔
83
  isValidNewOption: <Option, Group extends GroupBase<Option>>(
84
    inputValue: string,
85
    selectValue: Options<Option>,
86
    selectOptions: OptionsOrGroups<Option, Group>,
87
    accessors: Accessors<Option>
88
  ) =>
89
    !(
48✔
90
      !inputValue ||
92✔
91
      selectValue.some((option) =>
92
        compareOption(inputValue, option, accessors)
×
93
      ) ||
94
      selectOptions.some((option) =>
95
        compareOption(inputValue, option as Option, accessors)
184✔
96
      )
97
    ),
98
  getNewOptionData: (inputValue: string, optionLabel: ReactNode) => ({
16✔
99
    label: optionLabel,
100
    value: inputValue,
101
    __isNew__: true,
102
  }),
103
};
104

105
export default function useCreatable<
106
  Option,
107
  IsMulti extends boolean,
108
  Group extends GroupBase<Option>
109
>({
110
  allowCreateWhileLoading = false,
55✔
111
  createOptionPosition = 'last',
55✔
112
  formatCreateLabel = builtins.formatCreateLabel,
51✔
113
  isValidNewOption = builtins.isValidNewOption,
49✔
114
  // @ts-ignore
115
  getNewOptionData = builtins.getNewOptionData,
51✔
116
  onCreateOption,
117
  options: propsOptions = [],
1✔
118
  onChange: propsOnChange,
119
  ...restSelectProps
120
}: BaseCreatableProps<Option, IsMulti, Group>): PublicBaseSelectProps<
121
  Option,
122
  IsMulti,
123
  Group
124
> {
125
  const {
126
    getOptionValue = baseGetOptionValue,
51✔
127
    getOptionLabel = baseGetOptionLabel,
51✔
128
    inputValue,
129
    isLoading,
130
    isMulti,
131
    value,
132
    name,
133
  } = restSelectProps;
55✔
134

135
  const newOption = useMemo(
55✔
136
    () =>
137
      isValidNewOption(inputValue, cleanValue(value), propsOptions, {
54✔
138
        getOptionValue,
139
        getOptionLabel,
140
      })
141
        ? getNewOptionData(inputValue, formatCreateLabel(inputValue))
142
        : undefined,
143
    [
144
      formatCreateLabel,
145
      getNewOptionData,
146
      getOptionLabel,
147
      getOptionValue,
148
      inputValue,
149
      isValidNewOption,
150
      propsOptions,
151
      value,
152
    ]
153
  );
154

155
  const options = useMemo(
55✔
156
    () =>
157
      (allowCreateWhileLoading || !isLoading) && newOption
48✔
158
        ? createOptionPosition === 'first'
17!
159
          ? [newOption, ...propsOptions]
160
          : [...propsOptions, newOption]
161
        : propsOptions,
162
    [
163
      allowCreateWhileLoading,
164
      createOptionPosition,
165
      isLoading,
166
      newOption,
167
      propsOptions,
168
    ]
169
  );
170

171
  const onChange = useCallback(
55✔
172
    (
173
      newValue: OnChangeValue<Option, IsMulti>,
174
      actionMeta: ActionMeta<Option>
175
    ) => {
176
      if (actionMeta.action !== 'select-option') {
×
177
        return propsOnChange(newValue, actionMeta);
×
178
      }
179
      const valueArray = Array.isArray(newValue) ? newValue : [newValue];
×
180

181
      if (valueArray[valueArray.length - 1] === newOption) {
×
182
        if (onCreateOption) onCreateOption(inputValue);
×
183
        else {
184
          const newOptionData = getNewOptionData(inputValue, inputValue);
×
185
          const newActionMeta: ActionMeta<Option> = {
×
186
            action: 'create-option',
187
            name,
188
            option: newOptionData,
189
          };
190
          propsOnChange(
×
191
            valueTernary(
192
              isMulti,
193
              [...cleanValue(value), newOptionData],
194
              newOptionData
195
            ),
196
            newActionMeta
197
          );
198
        }
199
        return;
×
200
      }
201
      propsOnChange(newValue, actionMeta);
×
202
    },
203
    [
204
      getNewOptionData,
205
      inputValue,
206
      isMulti,
207
      name,
208
      newOption,
209
      onCreateOption,
210
      propsOnChange,
211
      value,
212
    ]
213
  );
214

215
  return {
55✔
216
    ...restSelectProps,
217
    options,
218
    onChange,
219
  };
220
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc