diff --git a/src/components/Picker/BasePicker.js b/src/components/Picker/BasePicker.js
index d7fbb2ed8d6b..173b863edfcc 100644
--- a/src/components/Picker/BasePicker.js
+++ b/src/components/Picker/BasePicker.js
@@ -1,5 +1,5 @@
import _ from 'underscore';
-import React, {PureComponent} from 'react';
+import React, {useContext, useEffect, useImperativeHandle, useRef, useState} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import RNPickerSelect from 'react-native-picker-select';
@@ -12,6 +12,9 @@ import themeColors from '../../styles/themes/default';
import {ScrollContext} from '../ScrollViewWithContext';
const propTypes = {
+ /** A forwarded ref */
+ forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]),
+
/** BasePicker label */
label: PropTypes.string,
@@ -81,6 +84,7 @@ const propTypes = {
};
const defaultProps = {
+ forwardedRef: undefined,
label: '',
isDisabled: false,
errorText: '',
@@ -104,204 +108,186 @@ const defaultProps = {
additionalPickerEvents: () => {},
};
-/**
- * @property {View} root - a reference to the root View
- * @property {Object} picker - a reference to @react-native-picker/picker
- */
-class BasePicker extends PureComponent {
- constructor(props) {
- super(props);
- this.state = {
- isHighlighted: false,
- };
-
- this.onInputChange = this.onInputChange.bind(this);
- this.enableHighlight = this.enableHighlight.bind(this);
- this.disableHighlight = this.disableHighlight.bind(this);
- this.focus = this.focus.bind(this);
- this.measureLayout = this.measureLayout.bind(this);
-
- // Windows will reuse the text color of the select for each one of the options
- // so we might need to color accordingly so it doesn't blend with the background.
- this.placeholder = _.isEmpty(this.props.placeholder)
- ? {}
- : {
- ...this.props.placeholder,
- color: themeColors.pickerOptionsTextColor,
- };
- }
+function BasePicker(props) {
+ const [isHighlighted, setIsHighlighted] = useState(false);
- componentDidMount() {
- this.setDefaultValue();
- }
+ // reference to the root View
+ const root = useRef(null);
- componentDidUpdate(prevProps) {
- if (prevProps.items === this.props.items) {
- return;
- }
- this.setDefaultValue();
- }
+ // reference to @react-native-picker/picker
+ const picker = useRef(null);
- /**
- * Forms use inputID to set values. But BasePicker passes an index as the second parameter to onInputChange
- * We are overriding this behavior to make BasePicker work with Form
- * @param {String} value
- * @param {Number} index
- */
- onInputChange(value, index) {
- if (this.props.inputID) {
- this.props.onInputChange(value);
+ // Windows will reuse the text color of the select for each one of the options
+ // so we might need to color accordingly so it doesn't blend with the background.
+ const placeholder = _.isEmpty(props.placeholder)
+ ? {}
+ : {
+ ...props.placeholder,
+ color: themeColors.pickerOptionsTextColor,
+ };
+
+ useEffect(() => {
+ if (props.value || !props.items || props.items.length !== 1 || !props.onInputChange) {
return;
}
- this.props.onInputChange(value, index);
- }
-
- setDefaultValue() {
// When there is only 1 element in the selector, we do the user a favor and automatically select it for them
// so they don't have to spend extra time selecting the only possible value.
- if (this.props.value || !this.props.items || this.props.items.length !== 1 || !this.props.onInputChange) {
- return;
- }
- this.props.onInputChange(this.props.items[0].value, 0);
- }
+ props.onInputChange(props.items[0].value, 0);
- enableHighlight() {
- this.setState({
- isHighlighted: true,
- });
- }
-
- disableHighlight() {
- this.setState({
- isHighlighted: false,
- });
- }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [props.items]);
- /**
- * Focuses the picker (if configured to do so)
- *
- * This method is used by Form
- */
- focus() {
- if (!this.props.shouldFocusPicker) {
- return;
- }
-
- // Defer the focusing to work around a bug on Mobile Safari, where focusing the `select` element in the same
- // task when we scrolled to it left that element in a glitched state, where the dropdown list can't be opened
- // until the element gets re-focused
- _.defer(() => {
- this.picker.focus();
- });
- }
+ const context = useContext(ScrollContext);
/**
- * Like measure(), but measures the view relative to an ancestor
- *
- * This method is used by Form when scrolling to the input
- *
- * @param {Object} relativeToNativeComponentRef - reference to an ancestor
- * @param {function(x: number, y: number, width: number, height: number): void} onSuccess - callback called on success
- * @param {function(): void} onFail - callback called on failure
+ * Forms use inputID to set values. But BasePicker passes an index as the second parameter to onInputChange
+ * We are overriding this behavior to make BasePicker work with Form
+ * @param {String} value
+ * @param {Number} index
*/
- measureLayout(relativeToNativeComponentRef, onSuccess, onFail) {
- if (!this.root) {
+ const onInputChange = (value, index) => {
+ if (props.inputID) {
+ props.onInputChange(value);
return;
}
- this.root.measureLayout(relativeToNativeComponentRef, onSuccess, onFail);
- }
-
- render() {
- const hasError = !_.isEmpty(this.props.errorText);
-
- if (this.props.isDisabled) {
- return (
-
- {Boolean(this.props.label) && (
-
- {this.props.label}
-
- )}
- {this.props.value}
- {Boolean(this.props.hintText) && {this.props.hintText}}
-
- );
- }
-
+ props.onInputChange(value, index);
+ };
+
+ const enableHighlight = () => {
+ setIsHighlighted(true);
+ };
+
+ const disableHighlight = () => {
+ setIsHighlighted(false);
+ };
+
+ useImperativeHandle(props.forwardedRef, () => ({
+ /**
+ * Focuses the picker (if configured to do so)
+ *
+ * This method is used by Form
+ */
+ focus() {
+ if (!props.shouldFocusPicker) {
+ return;
+ }
+
+ // Defer the focusing to work around a bug on Mobile Safari, where focusing the `select` element in the
+ // same task when we scrolled to it left that element in a glitched state, where the dropdown list can't
+ // be opened until the element gets re-focused
+ _.defer(() => {
+ picker.current.focus();
+ });
+ },
+
+ /**
+ * Like measure(), but measures the view relative to an ancestor
+ *
+ * This method is used by Form when scrolling to the input
+ *
+ * @param {Object} relativeToNativeComponentRef - reference to an ancestor
+ * @param {function(x: number, y: number, width: number, height: number): void} onSuccess - callback called on success
+ * @param {function(): void} onFail - callback called on failure
+ */
+ measureLayout(relativeToNativeComponentRef, onSuccess, onFail) {
+ if (!root.current) {
+ return;
+ }
+
+ root.current.measureLayout(relativeToNativeComponentRef, onSuccess, onFail);
+ },
+ }));
+
+ const hasError = !_.isEmpty(props.errorText);
+
+ if (props.isDisabled) {
return (
- <>
- (this.root = el)}
- style={[
- styles.pickerContainer,
- this.props.isDisabled && styles.inputDisabled,
- ...this.props.containerStyles,
- this.state.isHighlighted && styles.borderColorFocus,
- hasError && styles.borderColorDanger,
- ]}
- >
- {this.props.label && (
-
- {this.props.label}
-
- )}
- ({...item, color: themeColors.pickerOptionsTextColor}))}
- style={this.props.size === 'normal' ? styles.picker(this.props.isDisabled, this.props.backgroundColor) : styles.pickerSmall(this.props.backgroundColor)}
- useNativeAndroidPickerStyle={false}
- placeholder={this.placeholder}
- value={this.props.value}
- Icon={() => this.props.icon(this.props.size)}
- disabled={this.props.isDisabled}
- fixAndroidTouchableBug
- onOpen={this.enableHighlight}
- onClose={this.disableHighlight}
- textInputProps={{
- allowFontScaling: false,
- }}
- pickerProps={{
- ref: (el) => (this.picker = el),
- onFocus: this.enableHighlight,
- onBlur: () => {
- this.disableHighlight();
- this.props.onBlur();
- },
- ...this.props.additionalPickerEvents(this.enableHighlight, (value, index) => {
- this.onInputChange(value, index);
- this.disableHighlight();
- }),
- }}
- scrollViewRef={this.context && this.context.scrollViewRef}
- scrollViewContentOffsetY={this.context && this.context.contentOffsetY}
- />
-
-
- {Boolean(this.props.hintText) && {this.props.hintText}}
- >
+
+ {Boolean(props.label) && (
+
+ {props.label}
+
+ )}
+ {props.value}
+ {Boolean(props.hintText) && {props.hintText}}
+
);
}
+
+ return (
+ <>
+
+ {props.label && (
+
+ {props.label}
+
+ )}
+ ({...item, color: themeColors.pickerOptionsTextColor}))}
+ style={props.size === 'normal' ? styles.picker(props.isDisabled, props.backgroundColor) : styles.pickerSmall(props.backgroundColor)}
+ useNativeAndroidPickerStyle={false}
+ placeholder={placeholder}
+ value={props.value}
+ Icon={() => props.icon(props.size)}
+ disabled={props.isDisabled}
+ fixAndroidTouchableBug
+ onOpen={enableHighlight}
+ onClose={disableHighlight}
+ textInputProps={{
+ allowFontScaling: false,
+ }}
+ pickerProps={{
+ ref: picker,
+ onFocus: enableHighlight,
+ onBlur: () => {
+ disableHighlight();
+ props.onBlur();
+ },
+ ...props.additionalPickerEvents(enableHighlight, (value, index) => {
+ onInputChange(value, index);
+ disableHighlight();
+ }),
+ }}
+ scrollViewRef={context && context.scrollViewRef}
+ scrollViewContentOffsetY={context && context.contentOffsetY}
+ />
+
+
+ {Boolean(props.hintText) && {props.hintText}}
+ >
+ );
}
BasePicker.propTypes = propTypes;
BasePicker.defaultProps = defaultProps;
-BasePicker.contextType = ScrollContext;
+BasePicker.displayName = 'BasePicker';
export default React.forwardRef((props, ref) => (
));