diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index 2cb3df16232f..dac2f8cd7c8d 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -9,6 +9,7 @@ import styles from '../styles/styles'; import TextInput from './TextInput'; import Log from '../libs/Log'; import * as GooglePlacesUtils from '../libs/GooglePlacesUtils'; +import * as FormUtils from '../libs/FormUtils'; // The error that's being thrown below will be ignored until we fork the // react-native-google-places-autocomplete repo and replace the @@ -16,6 +17,26 @@ import * as GooglePlacesUtils from '../libs/GooglePlacesUtils'; LogBox.ignoreLogs(['VirtualizedLists should never be nested']); const propTypes = { + /** Indicates that the input is being used with the Form component */ + isFormInput: PropTypes.bool, + + /** + * The ID used to uniquely identify the input + * + * @param {Object} props - props passed to the input + * @returns {Object} - returns an Error object if isFormInput is supplied but inputID is falsey or not a string + */ + inputID: props => FormUtils.validateInputIDProps(props), + + /** Saves a draft of the input value when used in a form */ + shouldSaveDraft: PropTypes.bool, + + /** Callback that is called when the text input is blurred */ + onBlur: PropTypes.func, + + /** Error text to display */ + errorText: PropTypes.string, + /** The label to display for the field */ label: PropTypes.string.isRequired, @@ -32,7 +53,12 @@ const propTypes = { }; const defaultProps = { - value: '', + isFormInput: false, + inputID: undefined, + shouldSaveDraft: false, + onBlur: () => {}, + errorText: '', + value: undefined, containerStyles: [], }; @@ -66,7 +92,7 @@ const AddressSearch = (props) => { const state = GooglePlacesUtils.getAddressComponent(addressComponents, 'administrative_area_level_1', 'short_name'); const values = {}; - if (street && street.length > props.value.trim().length) { + if (street && props.value && street.length > props.value.trim().length) { // We are only passing the street number and name if the combined length is longer than the value // that was initially passed to the autocomplete component. Google Places can truncate details // like Apt # and this is the best way we have to tell that the new value it's giving us is less @@ -111,10 +137,27 @@ const AddressSearch = (props) => { }} textInputProps={{ InputComp: TextInput, + ref: (node) => { + if (!props.innerRef) { + return; + } + + if (_.isFunction(props.innerRef)) { + props.innerRef(node); + return; + } + + // eslint-disable-next-line no-param-reassign + props.innerRef.current = node; + }, label: props.label, containerStyles: props.containerStyles, errorText: props.errorText, value: props.value, + isFormInput: props.isFormInput, + inputID: props.inputID, + shouldSaveDraft: props.shouldSaveDraft, + onBlur: props.onBlur, onChangeText: (text) => { if (skippedFirstOnChangeTextRef.current) { props.onChange({street: text}); @@ -160,4 +203,7 @@ const AddressSearch = (props) => { AddressSearch.propTypes = propTypes; AddressSearch.defaultProps = defaultProps; -export default withLocalize(AddressSearch); +export default withLocalize(React.forwardRef((props, ref) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + +))); diff --git a/src/stories/AddressSearch.stories.js b/src/stories/AddressSearch.stories.js new file mode 100644 index 000000000000..6f03680c47f7 --- /dev/null +++ b/src/stories/AddressSearch.stories.js @@ -0,0 +1,42 @@ +import React, {useState} from 'react'; +import AddressSearch from '../components/AddressSearch'; + +/** + * We use the Component Story Format for writing stories. Follow the docs here: + * + * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format + */ +export default { + title: 'Components/AddressSearch', + component: AddressSearch, + args: { + label: 'Enter street', + errorText: '', + }, +}; + +const Template = (args) => { + const [value, setValue] = useState(''); + return ( + setValue(street)} + // eslint-disable-next-line react/jsx-props-no-spreading + {...args} + /> + ); +}; + +// Arguments can be passed to the component by binding +// See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +const Default = Template.bind({}); + +const ErrorStory = Template.bind({}); +ErrorStory.args = { + errorText: 'The street you are looking for does not exist', +}; + +export { + Default, + ErrorStory, +}; diff --git a/src/stories/Form.stories.js b/src/stories/Form.stories.js index f8b0a9b8cbe3..a9f2ffbd5e56 100644 --- a/src/stories/Form.stories.js +++ b/src/stories/Form.stories.js @@ -1,6 +1,7 @@ import React from 'react'; import {View} from 'react-native'; import TextInput from '../components/TextInput'; +import AddressSearch from '../components/AddressSearch'; import Form from '../components/Form'; import * as FormActions from '../libs/actions/FormActions'; import styles from '../styles/styles'; @@ -13,7 +14,7 @@ import styles from '../styles/styles'; const story = { title: 'Components/Form', component: Form, - subcomponents: {TextInput}, + subcomponents: {TextInput, AddressSearch}, }; const Template = (args) => { @@ -39,6 +40,12 @@ const Template = (args) => { containerStyles={[styles.mt4]} isFormInput /> + ); };