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' ;
12+ import useIsMounted from '../hooks/use-is-mounted' ;
713
814const getSelectValue = ( stateValue , simpleValue , isMulti , allOptions ) =>
915 simpleValue ? allOptions . filter ( ( { value } ) => ( isMulti ? stateValue . includes ( value ) : isEqual ( value , stateValue ) ) ) : stateValue ;
@@ -15,65 +21,165 @@ const handleSelectChange = (option, simpleValue, isMulti, onChange) => {
1521 : onChange ( sanitizedOption ) ;
1622} ;
1723
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 ;
24+ const selectProvider = {
25+ createable : CreatableSelect
26+ } ;
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+ loadOptionsChangeCounter,
44+ ...props
45+ } ) => {
46+ const [ state , dispatch ] = useReducer ( reducer , {
47+ isLoading : false ,
48+ options : propsOptions ,
49+ promises : { } ,
50+ isInitialLoaded : false
51+ } ) ;
52+
53+ const isMounted = useIsMounted ( ) ;
54+
55+ const updateOptions = ( ) => {
56+ dispatch ( { type : 'startLoading' } ) ;
57+
58+ return loadOptions ( ) . then ( ( data ) => {
59+ if ( isMounted ) {
60+ if ( value && Array . isArray ( value ) ) {
61+ const selectValue = value . filter ( ( value ) =>
62+ typeof value === 'object' ? data . find ( ( option ) => value . value === option . value ) : data . find ( ( option ) => value === option . value )
63+ ) ;
64+ onChange ( selectValue . length === 0 ? undefined : selectValue ) ;
65+ } else if ( value && ! data . find ( ( { value : internalValue } ) => internalValue === value ) ) {
66+ onChange ( undefined ) ;
67+ }
68+
69+ dispatch ( { type : 'updateOptions' , payload : data } ) ;
70+ }
71+ } ) ;
72+ } ;
73+
74+ useEffect ( ( ) => {
75+ if ( loadOptions ) {
76+ updateOptions ( ) ;
77+ }
2278
23- const selectValue = pluckSingleValue ? ( isMulti ? value : Array . isArray ( value ) && value [ 0 ] ? value [ 0 ] : value ) : value ;
79+ dispatch ( { type : 'initialLoaded' } ) ;
80+ } , [ ] ) ;
2481
82+ const loadOptionsStr = loadOptions ? fnToString ( loadOptions ) : '' ;
83+
84+ useEffect ( ( ) => {
85+ if ( loadOptionsStr && state . isInitialLoaded ) {
86+ updateOptions ( ) ;
87+ }
88+ } , [ loadOptionsStr , loadOptionsChangeCounter ] ) ;
89+
90+ useEffect ( ( ) => {
91+ if ( state . isInitialLoaded ) {
92+ if ( value && ! propsOptions . map ( ( { value } ) => value ) . includes ( value ) ) {
93+ onChange ( undefined ) ;
94+ }
95+
96+ dispatch ( { type : 'setOptions' , payload : propsOptions } ) ;
97+ }
98+ } , [ propsOptions ] ) ;
99+
100+ if ( state . isLoading ) {
25101 return (
26102 < ReactSelect
27- className = { clsx ( classNamePrefix , {
28- 'has-error' : invalid
29- } ) }
30103 { ...props }
31- { ...inputProps }
32- options = { options }
33104 classNamePrefix = { classNamePrefix }
34- isMulti = { isMulti }
35- value = { getSelectValue ( selectValue , simpleValue , isMulti , options ) }
36- onChange = { ( option ) => handleSelectChange ( option , simpleValue , isMulti , onChange ) }
105+ isDisabled = { true }
106+ placeholder = { loadingMessage }
107+ options = { state . options }
108+ { ...loadingProps }
37109 />
38110 ) ;
39111 }
40- }
112+
113+ const onInputChange = ( inputValue ) => {
114+ if ( inputValue && loadOptions && state . promises [ inputValue ] === undefined && props . isSearchable ) {
115+ dispatch ( { type : 'setPromises' , payload : { [ inputValue ] : true } } ) ;
116+
117+ loadOptions ( inputValue )
118+ . then ( ( options ) => {
119+ if ( isMounted ) {
120+ dispatch ( {
121+ type : 'setPromises' ,
122+ payload : { [ inputValue ] : false } ,
123+ options
124+ } ) ;
125+ }
126+ } )
127+ . catch ( ( error ) => {
128+ dispatch ( { type : 'setPromises' , payload : { [ inputValue ] : false } } ) ;
129+ throw error ;
130+ } ) ;
131+ }
132+ } ;
133+
134+ const renderNoOptionsMessage = ( ) => ( Object . values ( state . promises ) . some ( ( value ) => value ) ? ( ) => updatingMessage : ( ) => noOptionsMessage ) ;
135+
136+ const selectValue = pluckSingleValue ? ( isMulti ? value : Array . isArray ( value ) && value [ 0 ] ? value [ 0 ] : value ) : value ;
137+
138+ const SelectFinal = selectProvider [ selectVariant ] || ReactSelect ;
139+
140+ return (
141+ < SelectFinal
142+ className = { clsx ( classNamePrefix , {
143+ 'has-error' : invalid
144+ } ) }
145+ { ...props }
146+ isDisabled = { props . isDisabled || props . isReadOnly }
147+ options = { state . options }
148+ classNamePrefix = { classNamePrefix }
149+ isMulti = { isMulti }
150+ value = { getSelectValue ( selectValue , simpleValue , isMulti , state . options ) }
151+ onChange = { ( option ) => handleSelectChange ( option , simpleValue , isMulti , onChange ) }
152+ onInputChange = { onInputChange }
153+ isFetching = { Object . values ( state . promises ) . some ( ( value ) => value ) }
154+ noOptionsMessage = { renderNoOptionsMessage ( ) }
155+ hideSelectedOptions = { false }
156+ closeMenuOnSelect = { ! isMulti }
157+ />
158+ ) ;
159+ } ;
41160
42161Select . propTypes = {
43162 options : PropTypes . array ,
44163 onChange : PropTypes . func ,
45- classNamePrefix : PropTypes . string . isRequired ,
164+ classNamePrefix : PropTypes . string ,
46165 invalid : PropTypes . bool ,
47166 simpleValue : PropTypes . bool ,
48167 isMulti : PropTypes . bool ,
49168 pluckSingleValue : PropTypes . bool ,
50- input
169+ value : PropTypes . any ,
170+ placeholder : PropTypes . string ,
171+ loadOptionsChangeCounter : PropTypes . number ,
172+ ...input
51173} ;
52174
53175Select . defaultProps = {
54176 options : [ ] ,
55177 invalid : false ,
56178 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 = {
179+ pluckSingleValue : true ,
74180 placeholder : 'Choose...' ,
75181 isSearchable : false ,
76182 isClearable : false
77183} ;
78184
79- export default DataDrivenSelect ;
185+ export default Select ;
0 commit comments