Skip to content
27 changes: 19 additions & 8 deletions src/components/TextInputFocusable/index.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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
// <constructor ref={el => 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();
}
}
Expand Down Expand Up @@ -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() {
Expand All @@ -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 */
<TextInputFocusable {...props} forwardedRef={ref} />
));
38 changes: 35 additions & 3 deletions src/components/TextInputFocusable/index.native.js
Original file line number Diff line number Diff line change
@@ -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 => (<TextInput maxHeight={116} {...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
// <constructor ref={el => 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 (
<TextInput
ref={el => 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 */
<TextInputFocusable {...props} forwardedRef={ref} />
));
30 changes: 27 additions & 3 deletions src/page/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB, it's bugging me a little that we can't do this in the constructor but not much we can do right now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's a little annoying but its the best i could think off such that we could still use debounce and have this.comment always be up to date.

}

/**
* 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 || '');
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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
Expand All @@ -78,6 +100,7 @@ class ReportActionCompose extends React.Component {
}

this.props.onSubmit(trimmedComment);
this.textInput.clear();
this.updateComment('');
}

Expand All @@ -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
/>
<TouchableOpacity
Expand Down