1- import React , { Component } from 'react' ;
1+ /* eslint-disable react-hooks/exhaustive-deps */
2+ import React , { useEffect , useReducer } from 'react' ;
23import ReactSelect from 'react-select' ;
4+ import CreatableSelect from 'react-select/creatable' ;
5+
36import PropTypes from 'prop-types' ;
47import clsx from 'clsx' ;
58import isEqual from 'lodash/isEqual' ;
69import { input } from '../prop-types-templates' ;
10+ import fnToString from '../utils/fn-to-string' ;
11+ import reducer from './reducer' ;
712
813const getSelectValue = ( stateValue , simpleValue , isMulti , allOptions ) =>
914 simpleValue ? allOptions . filter ( ( { value } ) => ( isMulti ? stateValue . includes ( value ) : isEqual ( value , stateValue ) ) ) : stateValue ;
@@ -15,29 +20,132 @@ const handleSelectChange = (option, simpleValue, isMulti, onChange) => {
1520 : onChange ( sanitizedOption ) ;
1621} ;
1722
18- class Select extends Component {
19- render ( ) {
20- const { input, invalid, classNamePrefix, simpleValue, isMulti, pluckSingleValue, options, ...props } = this . props ;
21- const { value, onChange, ...inputProps } = input ;
22-
23- const selectValue = pluckSingleValue ? ( isMulti ? value : Array . isArray ( value ) && value [ 0 ] ? value [ 0 ] : value ) : value ;
24-
25- return (
26- < ReactSelect
27- className = { clsx ( classNamePrefix , {
28- 'has-error' : invalid
29- } ) }
30- { ...props }
31- { ...inputProps }
32- options = { options }
33- classNamePrefix = { classNamePrefix }
34- isMulti = { isMulti }
35- value = { getSelectValue ( selectValue , simpleValue , isMulti , options ) }
36- onChange = { ( option ) => handleSelectChange ( option , simpleValue , isMulti , onChange ) }
37- />
38- ) ;
23+ const selectProvider = ( type ) =>
24+ ( {
25+ createable : CreatableSelect
26+ } [ type ] || ReactSelect ) ;
27+
28+ const Select = ( {
29+ invalid,
30+ classNamePrefix,
31+ simpleValue,
32+ isMulti,
33+ pluckSingleValue,
34+ options : propsOptions ,
35+ loadOptions,
36+ loadingMessage,
37+ loadingProps,
38+ selectVariant,
39+ updatingMessage,
40+ noOptionsMessage,
41+ value,
42+ onChange,
43+ ...props
44+ } ) => {
45+ const [ state , dispatch ] = useReducer ( reducer , {
46+ isLoading : false ,
47+ options : propsOptions ,
48+ promises : { } ,
49+ isMounted : false
50+ } ) ;
51+
52+ const updateOptions = ( ) => {
53+ dispatch ( { type : 'startLoading' } ) ;
54+
55+ return loadOptions ( ) . then ( ( data ) => {
56+ if ( Array . isArray ( value ) ) {
57+ const selectValue = value . filter ( ( value ) =>
58+ typeof value === 'object' ? data . find ( ( option ) => value . value === option . value ) : data . find ( ( option ) => value === option . value )
59+ ) ;
60+ onChange ( selectValue . length === 0 ? undefined : selectValue ) ;
61+ } else if ( ! data . find ( ( { value : internalValue } ) => internalValue === value ) ) {
62+ onChange ( undefined ) ;
63+ }
64+
65+ dispatch ( { type : 'updateOptions' , payload : data } ) ;
66+ } ) ;
67+ } ;
68+
69+ useEffect ( ( ) => {
70+ if ( loadOptions ) {
71+ updateOptions ( ) ;
72+ }
73+
74+ dispatch ( { type : 'mounted' } ) ;
75+
76+ return ( ) => {
77+ dispatch ( { type : 'unmounted' } ) ;
78+ } ;
79+ } , [ ] ) ;
80+
81+ const loadOptionsStr = loadOptions ? fnToString ( loadOptions ) : '' ;
82+
83+ useEffect ( ( ) => {
84+ if ( loadOptionsStr && state . isMounted ) {
85+ updateOptions ( ) ;
86+ }
87+ } , [ loadOptionsStr ] ) ;
88+
89+ useEffect ( ( ) => {
90+ if ( state . isMounted ) {
91+ if ( ! propsOptions . map ( ( { value } ) => value ) . includes ( value ) ) {
92+ onChange ( undefined ) ;
93+ }
94+
95+ dispatch ( { type : 'setOptions' , payload : propsOptions } ) ;
96+ }
97+ } , [ propsOptions ] ) ;
98+
99+ if ( state . isLoading ) {
100+ return < ReactSelect isDisabled = { true } placeholder = { loadingMessage } options = { state . options } { ...loadingProps } /> ;
39101 }
40- }
102+
103+ const onInputChange = ( inputValue ) => {
104+ if ( loadOptions && state . promises [ inputValue ] === undefined ) {
105+ dispatch ( { type : 'setPromises' , payload : { [ inputValue ] : true } } ) ;
106+
107+ loadOptions ( inputValue )
108+ . then ( ( options ) => {
109+ if ( state . isMounted ) {
110+ dispatch ( {
111+ type : 'setPromises' ,
112+ payload : { [ inputValue ] : false } ,
113+ options
114+ } ) ;
115+ }
116+ } )
117+ . catch ( ( error ) => {
118+ dispatch ( { type : 'setPromises' , payload : { [ inputValue ] : false } } ) ;
119+ throw error ;
120+ } ) ;
121+ }
122+ } ;
123+
124+ const renderNoOptionsMessage = ( ) => ( Object . values ( state . promises ) . some ( ( value ) => value ) ? ( ) => updatingMessage : ( ) => noOptionsMessage ) ;
125+
126+ const selectValue = pluckSingleValue ? ( isMulti ? value : Array . isArray ( value ) && value [ 0 ] ? value [ 0 ] : value ) : value ;
127+
128+ const SelectFinal = selectProvider ( selectVariant ) ;
129+
130+ return (
131+ < SelectFinal
132+ className = { clsx ( classNamePrefix , {
133+ 'has-error' : invalid
134+ } ) }
135+ { ...props }
136+ options = { state . options }
137+ classNamePrefix = { classNamePrefix }
138+ isMulti = { isMulti }
139+ value = { getSelectValue ( selectValue , simpleValue , isMulti , state . options ) }
140+ onChange = { ( option ) => handleSelectChange ( option , simpleValue , isMulti , onChange ) }
141+ onInputChange = { onInputChange }
142+ isFetching = { Object . values ( state . promises ) . some ( ( value ) => value ) }
143+ noOptionsMessage = { renderNoOptionsMessage ( ) }
144+ hideSelectedOptions = { false }
145+ closeMenuOnSelect = { ! isMulti }
146+ />
147+ ) ;
148+ } ;
41149
42150Select . propTypes = {
43151 options : PropTypes . array ,
@@ -47,33 +155,19 @@ Select.propTypes = {
47155 simpleValue : PropTypes . bool ,
48156 isMulti : PropTypes . bool ,
49157 pluckSingleValue : PropTypes . bool ,
50- input
158+ value : PropTypes . any ,
159+ placeholder : PropTypes . string ,
160+ ...input
51161} ;
52162
53163Select . defaultProps = {
54164 options : [ ] ,
55165 invalid : false ,
56166 simpleValue : true ,
57- pluckSingleValue : true
58- } ;
59-
60- const DataDrivenSelect = ( { isMulti, ...props } ) => {
61- const closeMenuOnSelect = ! isMulti ;
62- return < Select hideSelectedOptions = { false } isMulti = { isMulti } { ...props } closeMenuOnSelect = { closeMenuOnSelect } /> ;
63- } ;
64-
65- DataDrivenSelect . propTypes = {
66- value : PropTypes . any ,
67- onChange : PropTypes . func ,
68- isMulti : PropTypes . bool ,
69- placeholder : PropTypes . string ,
70- classNamePrefix : PropTypes . string . isRequired
71- } ;
72-
73- DataDrivenSelect . defaultProps = {
167+ pluckSingleValue : true ,
74168 placeholder : 'Choose...' ,
75169 isSearchable : false ,
76170 isClearable : false
77171} ;
78172
79- export default DataDrivenSelect ;
173+ export default Select ;
0 commit comments