diff --git a/src/components/TextInputFocusable/index.js b/src/components/TextInputFocusable/index.js index b60fa72a4d404..4d6b53df4d9c3 100644 --- a/src/components/TextInputFocusable/index.js +++ b/src/components/TextInputFocusable/index.js @@ -1,13 +1,17 @@ import React from 'react'; import {TextInput} from 'react-native'; import PropTypes from 'prop-types'; +import _ from 'underscore'; const propTypes = { + // A ref to forward to the text input + forwardedRef: PropTypes.func.isRequired, + // Maximum number of lines in the text input maxLines: PropTypes.number, - // The value of the comment box - value: PropTypes.string.isRequired, + // The default value of the comment box + defaultValue: PropTypes.string.isRequired, }; const defaultProps = { @@ -28,12 +32,20 @@ class TextInputFocusable extends React.Component { componentDidMount() { this.focusInput(); + + // This callback prop is used by the parent component using the constructor to + // get a ref to the inner textInput element e.g. if we do + // this.textInput = el} /> this will not + // return a ref to the component, but rather the HTML element by default + if (this.props.forwardedRef && _.isFunction(this.props.forwardedRef)) { + this.props.forwardedRef(this.textInput); + } } componentDidUpdate(prevProps) { this.focusInput(); - if (prevProps.value !== this.props.value) { + if (prevProps.defaultValue !== this.props.defaultValue) { this.updateNumberOfLines(); } } @@ -76,10 +88,6 @@ class TextInputFocusable extends React.Component { focusInput() { this.textInput.focus(); - if (this.props.value) { - this.textInput.selectionStart = this.props.value.length; - this.textInput.selectionEnd = this.props.value.length; - } } render() { @@ -100,4 +108,7 @@ class TextInputFocusable extends React.Component { TextInputFocusable.propTypes = propTypes; TextInputFocusable.defaultProps = defaultProps; -export default TextInputFocusable; +export default React.forwardRef((props, ref) => ( + /* eslint-disable-next-line react/jsx-props-no-spreading */ + +)); diff --git a/src/components/TextInputFocusable/index.native.js b/src/components/TextInputFocusable/index.native.js index c609e023b5e61..338445874c732 100644 --- a/src/components/TextInputFocusable/index.native.js +++ b/src/components/TextInputFocusable/index.native.js @@ -1,13 +1,45 @@ import React from 'react'; import {TextInput} from 'react-native'; +import PropTypes from 'prop-types'; +import _ from 'underscore'; + +const propTypes = { + // A ref to forward to the text input + forwardedRef: PropTypes.func.isRequired, +}; /** * On native layers we like to have the Text Input not focused so the user can read new chats without they keyboard in * the way of the view */ -// eslint-disable-next-line react/jsx-props-no-spreading -const TextInputFocusable = props => (); +class TextInputFocusable extends React.Component { + componentDidMount() { + // This callback prop is used by the parent component using the constructor to + // get a ref to the inner textInput element e.g. if we do + // this.textInput = el} /> this will not + // return a ref to the component, but rather the HTML element by default + if (this.props.forwardedRef && _.isFunction(this.props.forwardedRef)) { + this.props.forwardedRef(this.textInput); + } + } + + render() { + return ( + this.textInput = el} + maxHeight={116} + /* eslint-disable-next-line react/jsx-props-no-spreading */ + {...this.props} + /> + ); + } +} TextInputFocusable.displayName = 'TextInputFocusable'; +TextInputFocusable.propTypes = propTypes; -export default TextInputFocusable; +// export default TextInputFocusable; +export default React.forwardRef((props, ref) => ( + /* eslint-disable-next-line react/jsx-props-no-spreading */ + +)); diff --git a/src/page/home/report/ReportActionCompose.js b/src/page/home/report/ReportActionCompose.js index a92e454e80794..934c6a6a90e56 100644 --- a/src/page/home/report/ReportActionCompose.js +++ b/src/page/home/report/ReportActionCompose.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import {View, Image, TouchableOpacity} from 'react-native'; +import _ from 'underscore'; import styles, {colors} from '../../../style/StyleSheet'; import TextInputFocusable from '../../../components/TextInputFocusable'; import sendIcon from '../../../../assets/images/icon-send.png'; @@ -32,9 +33,29 @@ class ReportActionCompose extends React.Component { super(props); this.updateComment = this.updateComment.bind(this); + this.debouncedSaveReportComment = _.debounce(this.debouncedSaveReportComment.bind(this), 1000, false); this.submitForm = this.submitForm.bind(this); this.triggerSubmitShortcut = this.triggerSubmitShortcut.bind(this); this.submitForm = this.submitForm.bind(this); + this.comment = ''; + } + + componentDidUpdate(prevProps) { + // The first time the component loads the props is empty and the next time it may contain value. + // If it does let's update this.comment so that it matches the defaultValue that we show in textInput. + if (this.props.comment && prevProps.comment === '' && prevProps.comment !== this.props.comment) { + this.comment = this.props.comment; + } + } + + /** + * Save our report comment in Ion. We debounce this method in the constructor so that it's not called too often + * to update Ion and re-render this component. + * + * @param {string} comment + */ + debouncedSaveReportComment(comment) { + saveReportComment(this.props.reportID, comment || ''); } /** @@ -43,7 +64,8 @@ class ReportActionCompose extends React.Component { * @param {string} newComment */ updateComment(newComment) { - saveReportComment(this.props.reportID, newComment || ''); + this.comment = newComment; + this.debouncedSaveReportComment(newComment); } /** @@ -69,7 +91,7 @@ class ReportActionCompose extends React.Component { e.preventDefault(); } - const trimmedComment = this.props.comment.trim(); + const trimmedComment = this.comment.trim(); // Don't submit empty comments // @TODO show an error in the UI @@ -78,6 +100,7 @@ class ReportActionCompose extends React.Component { } this.props.onSubmit(trimmedComment); + this.textInput.clear(); this.updateComment(''); } @@ -101,11 +124,12 @@ class ReportActionCompose extends React.Component { multiline textAlignVertical="top" placeholder="Write something..." + ref={el => this.textInput = el} placeholderTextColor={colors.textSupporting} onChangeText={this.updateComment} onKeyPress={this.triggerSubmitShortcut} style={[styles.textInput, styles.textInputCompose, styles.flex4]} - value={this.props.comment || ''} + defaultValue={this.props.comment || ''} maxLines={16} // This is the same that slack has />