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

JedWatson / react-select / 9de42ca4-8c55-4031-8310-e7e0a2b0128b

26 Oct 2024 11:27AM CUT coverage: 75.844%. Remained the same
9de42ca4-8c55-4031-8310-e7e0a2b0128b

Pull #5880

circleci

lukebennett88
add box-sizing to border-box for RequiredInput

adding `required` would otherwise cause an extra (unstylable) component to be added which has some implicit padding from the user agent style sheet (inputs have padding) which could cause horizontal scrolling when the whole scroll field is 100% wide.
Pull Request #5880: add box-sizing to border-box for RequiredInput

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