diff --git a/src/components/Form.js b/src/components/Form.js index f42101700862..c641a99e1f4c 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -57,10 +57,10 @@ class Form extends React.Component { this.state = { errors: {}, + inputValues: {}, }; this.inputRefs = {}; - this.inputValues = {}; this.touchedInputs = {}; this.setTouchedInput = this.setTouchedInput.bind(this); @@ -87,12 +87,12 @@ class Form extends React.Component { )); // Validate form and return early if any errors are found - if (!_.isEmpty(this.validate(this.inputValues))) { + if (!_.isEmpty(this.validate(this.state.inputValues))) { return; } // Call submit handler - this.props.onSubmit(this.inputValues); + this.props.onSubmit(this.state.inputValues); } /** @@ -145,30 +145,31 @@ class Form extends React.Component { const defaultValue = this.props.draftValues[inputID] || child.props.defaultValue; // We want to initialize the input value if it's undefined - if (_.isUndefined(this.inputValues[inputID])) { - this.inputValues[inputID] = defaultValue; + if (_.isUndefined(this.state.inputValues[inputID])) { + this.state.inputValues[inputID] = defaultValue; } return React.cloneElement(child, { ref: node => this.inputRefs[inputID] = node, defaultValue, + value: this.state.inputValues[inputID], errorText: this.state.errors[inputID] || '', onBlur: () => { this.setTouchedInput(inputID); - this.validate(this.inputValues); + this.validate(this.state.inputValues); }, onInputChange: (value, key) => { const inputKey = key || inputID; - this.inputValues[inputKey] = value; - const inputRef = this.inputRefs[inputKey]; + this.setState(prevState => ({ + inputValues: { + ...prevState.inputValues, + [inputKey]: value, + }, + }), () => this.validate(this.state.inputValues)); - if (key && inputRef && _.isFunction(inputRef.setNativeProps)) { - inputRef.setNativeProps({value}); - } if (child.props.shouldSaveDraft) { FormActions.setDraftValues(this.props.formID, {[inputKey]: value}); } - this.validate(this.inputValues); }, }); }); diff --git a/src/components/InvertedFlatList/index.js b/src/components/InvertedFlatList/index.js index b92b27bd3449..4a109a3ea3c6 100644 --- a/src/components/InvertedFlatList/index.js +++ b/src/components/InvertedFlatList/index.js @@ -36,17 +36,13 @@ class InvertedFlatList extends React.Component { this.props.innerRef(this.list); } - if (this.list) { - this.list - .getScrollableNode() - .addEventListener('wheel', this.invertedWheelEvent); - - this.list.setNativeProps({ - style: { - transform: 'translate3d(0,0,0) scaleY(-1)', - }, - }); + if (!this.list) { + return; } + + this.list + .getScrollableNode() + .addEventListener('wheel', this.invertedWheelEvent); } componentWillUnmount() { @@ -67,6 +63,7 @@ class InvertedFlatList extends React.Component { ref={el => this.list = el} shouldMeasureItems contentContainerStyle={StyleSheet.compose(this.props.contentContainerStyle, styles.justifyContentEnd)} + style={styles.translate0InvertY} /> ); } diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index cbcb0faa415c..f30cf14781d6 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -45,6 +45,10 @@ class BaseOptionsSelector extends Component { this.state = { allOptions, focusedIndex: this.props.shouldTextInputAppearBelowOptions ? allOptions.length : 0, + selection: { + start: this.props.value.length, + end: this.props.value.length, + }, }; } @@ -206,7 +210,7 @@ class BaseOptionsSelector extends Component { selectRow(option, ref) { if (this.props.shouldFocusOnSelectRow) { // Input is permanently focused on native platforms, so we always highlight the text inside of it - this.textInput.setNativeProps({selection: {start: 0, end: this.props.value.length}}); + this.setState({selection: {start: 0, end: this.props.value.length}}); if (this.relatedTarget && ref === findNodeHandle(this.relatedTarget)) { this.textInput.focus(); } @@ -237,9 +241,6 @@ class BaseOptionsSelector extends Component { value={this.props.value} label={this.props.textInputLabel} onChangeText={(text) => { - if (this.props.shouldFocusOnSelectRow) { - this.textInput.setNativeProps({selection: null}); - } this.props.onChangeText(text); }} placeholder={this.props.placeholderText || this.props.translate('optionsSelector.nameEmailOrPhoneNumber')} @@ -251,6 +252,8 @@ class BaseOptionsSelector extends Component { }} selectTextOnFocus blurOnSubmit={Boolean(this.state.allOptions.length)} + selection={this.state.selection} + onSelectionChange={e => this.setState({selection: e.nativeEvent.selection})} /> ); const optionsList = this.props.shouldShowOptions ? ( diff --git a/src/components/Picker/BasePicker/index.js b/src/components/Picker/BasePicker/index.js index eb3021ab54a9..b451dc8eef2f 100644 --- a/src/components/Picker/BasePicker/index.js +++ b/src/components/Picker/BasePicker/index.js @@ -10,26 +10,7 @@ class BasePicker extends React.Component { constructor(props) { super(props); - this.pickerValue = this.props.defaultValue; - - this.updateSelectedValueAndExecuteOnChange = this.updateSelectedValueAndExecuteOnChange.bind(this); this.executeOnCloseAndOnBlur = this.executeOnCloseAndOnBlur.bind(this); - this.setNativeProps = this.setNativeProps.bind(this); - } - - /** - * This method mimicks RN's setNativeProps method. It's exposed to Picker's ref and can be used by other components - * to directly manipulate Picker's value when Picker is used as an uncontrolled input. - * - * @param {*} value - */ - setNativeProps({value}) { - this.pickerValue = value; - } - - updateSelectedValueAndExecuteOnChange(value) { - this.props.onInputChange(value); - this.pickerValue = value; } executeOnCloseAndOnBlur() { @@ -42,12 +23,12 @@ class BasePicker extends React.Component { const hasError = !_.isEmpty(this.props.errorText); return ( this.props.icon(this.props.size)} disabled={this.props.disabled} fixAndroidTouchableBug @@ -57,15 +38,11 @@ class BasePicker extends React.Component { onFocus: this.props.onOpen, onBlur: this.executeOnCloseAndOnBlur, }} - ref={(node) => { - if (!node || !_.isFunction(this.props.innerRef)) { + ref={el => { + if (!_.isFunction(this.props.innerRef)) { return; } - - this.props.innerRef(node); - - // eslint-disable-next-line no-param-reassign - node.setNativeProps = this.setNativeProps; + this.props.innerRef(el); }} /> ); diff --git a/src/components/Picker/index.js b/src/components/Picker/index.js index 81e80bea6541..929e2ad0ee77 100644 --- a/src/components/Picker/index.js +++ b/src/components/Picker/index.js @@ -29,6 +29,9 @@ const propTypes = { /** Saves a draft of the input value when used in a form */ shouldSaveDraft: PropTypes.bool, + + /** A callback method that is called when the value changes and it received the selected value as an argument */ + onInputChange: PropTypes.func.isRequired, }; const defaultProps = { @@ -47,6 +50,23 @@ class Picker extends PureComponent { this.state = { isOpen: false, }; + + this.onInputChange = this.onInputChange.bind(this); + } + + /** + * Forms use inputID to set values. But Picker passes an index as the second parameter to onInputChange + * We are overriding this behavior to make Picker work with Form + * @param {String} value + * @param {Number} index + */ + onInputChange(value, index) { + if (this.props.inputID) { + this.props.onInputChange(value, this.props.inputID); + return; + } + + this.props.onInputChange(value, index); } render() { @@ -72,6 +92,7 @@ class Picker extends PureComponent { value={this.props.value} // eslint-disable-next-line react/jsx-props-no-spreading {...pickerProps} + onInputChange={this.onInputChange} /> diff --git a/src/components/RoomNameInput.js b/src/components/RoomNameInput.js index 8bd14fe9408c..8516763ee88a 100644 --- a/src/components/RoomNameInput.js +++ b/src/components/RoomNameInput.js @@ -12,8 +12,8 @@ const propTypes = { /** Callback to execute when the text input is modified correctly */ onChangeText: PropTypes.func, - /** Initial room name to show in input field. This should include the '#' already prefixed to the name */ - initialValue: PropTypes.string, + /** Room name to show in input field. This should include the '#' already prefixed to the name */ + value: PropTypes.string, /** Whether we should show the input as disabled */ disabled: PropTypes.bool, @@ -50,7 +50,7 @@ const propTypes = { const defaultProps = { onChangeText: () => {}, - initialValue: '', + value: '', disabled: false, errorText: '', ...fullPolicyDefaultProps, @@ -101,7 +101,7 @@ class RoomNameInput extends Component { prefixCharacter={CONST.POLICY.ROOM_PREFIX} placeholder={this.props.translate('newRoomPage.social')} onChange={this.setModifiedRoomName} - defaultValue={this.props.initialValue.substring(1)} // Since the room name always starts with a prefix, we omit the first character to avoid displaying it twice. + value={this.props.value.substring(1)} // Since the room name always starts with a prefix, we omit the first character to avoid displaying it twice. errorText={this.props.errorText} autoCapitalize="none" /> diff --git a/src/components/SignInPageForm/index.js b/src/components/SignInPageForm/index.js index 4ee95d1cb81e..b38692aaedd5 100644 --- a/src/components/SignInPageForm/index.js +++ b/src/components/SignInPageForm/index.js @@ -8,10 +8,8 @@ class Form extends React.Component { } // Password Managers need these attributes to be able to identify the form elements properly. - this.form.setNativeProps({ - method: 'post', - action: '/', - }); + this.form.setAttribute('method', 'post'); + this.form.setAttribute('action', '/'); } render() { diff --git a/src/components/TextInput/TextInputLabel/index.js b/src/components/TextInput/TextInputLabel/index.js index b6f8ca998614..29ae00fdcb98 100644 --- a/src/components/TextInput/TextInputLabel/index.js +++ b/src/components/TextInput/TextInputLabel/index.js @@ -8,7 +8,7 @@ class TextInputLabel extends PureComponent { if (!this.props.for) { return; } - this.label.setNativeProps({for: this.props.for}); + this.label.setAttribute('for', this.props.for); } render() { diff --git a/src/components/TextInput/index.js b/src/components/TextInput/index.js index 744eedaecbf4..4cbed48ce314 100644 --- a/src/components/TextInput/index.js +++ b/src/components/TextInput/index.js @@ -7,11 +7,11 @@ import * as baseTextInputPropTypes from './baseTextInputPropTypes'; class TextInput extends React.Component { componentDidMount() { if (this.props.disableKeyboard) { - this.textInput.setNativeProps({inputmode: 'none'}); + this.textInput.setAttribute('inputmode', 'none'); } if (this.props.name) { - this.textInput.setNativeProps({name: this.props.name}); + this.textInput.setAttribute('name', this.props.name); } } diff --git a/src/pages/ReportSettingsPage.js b/src/pages/ReportSettingsPage.js index 9efc59dc940e..0ba0ea996449 100644 --- a/src/pages/ReportSettingsPage.js +++ b/src/pages/ReportSettingsPage.js @@ -125,11 +125,6 @@ class ReportSettingsPage extends Component { */ resetToPreviousName() { this.setState({newRoomName: this.props.report.reportName}); - - // Reset the input's value back to the previously saved report name - if (this.roomNameInputRef) { - this.roomNameInputRef.setNativeProps({text: this.props.report.reportName.replace(CONST.POLICY.ROOM_PREFIX, '')}); - } Report.clearPolicyRoomNameErrors(this.props.report.reportID); } @@ -229,7 +224,7 @@ class ReportSettingsPage extends Component { : ( this.roomNameInputRef = el} - initialValue={this.state.newRoomName} + value={this.state.newRoomName} policyID={linkedWorkspace && linkedWorkspace.id} errorText={this.state.errors.newRoomName} onChangeText={newRoomName => this.clearErrorAndSetValue('newRoomName', newRoomName)} diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js index a82101de219f..11616d76a97c 100755 --- a/src/pages/home/report/ReportActionCompose.js +++ b/src/pages/home/report/ReportActionCompose.js @@ -144,6 +144,7 @@ class ReportActionCompose extends React.Component { end: props.comment.length, }, maxLines: props.isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES, + value: props.comment, }; } @@ -311,9 +312,6 @@ class ReportActionCompose extends React.Component { addEmojiToTextBox(emoji) { const newComment = this.comment.slice(0, this.state.selection.start) + emoji + this.comment.slice(this.state.selection.end, this.comment.length); - this.textInput.setNativeProps({ - text: newComment, - }); this.setState(prevState => ({ selection: { start: prevState.selection.start + emoji.length, @@ -372,9 +370,9 @@ class ReportActionCompose extends React.Component { * @param {String} newComment */ updateComment(newComment) { - this.textInput.setNativeProps({text: newComment}); this.setState({ isCommentEmpty: !!newComment.match(/^(\s|`)*$/), + value: newComment, }); // Indicate that draft has been created. @@ -627,7 +625,6 @@ class ReportActionCompose extends React.Component { this.setState({isDraggingOver: false}); }} style={[styles.textInputCompose, this.props.isComposerFullSize ? styles.textInputFullCompose : styles.flex4]} - defaultValue={this.props.comment} maxLines={this.state.maxLines} onFocus={() => this.setIsFocused(true)} onBlur={() => this.setIsFocused(false)} @@ -640,6 +637,7 @@ class ReportActionCompose extends React.Component { isFullComposerAvailable={this.state.isFullComposerAvailable} setIsFullComposerAvailable={this.setIsFullComposerAvailable} isComposerFullSize={this.props.isComposerFullSize} + value={this.state.value} /> diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js index d3cb3ae0a3fc..8c844c50b516 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.js @@ -97,7 +97,6 @@ class ReportActionItemMessageEdit extends React.Component { * @param {String} newDraft */ updateDraft(newDraft) { - this.textInput.setNativeProps({text: newDraft}); this.setState({draft: newDraft}); // This component is rendered only when draft is set to a non-empty string. In order to prevent component diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js index 250a7e2021ff..dde95741bac8 100755 --- a/src/pages/settings/Profile/ProfilePage.js +++ b/src/pages/settings/Profile/ProfilePage.js @@ -260,7 +260,7 @@ class ProfilePage extends Component { value: '', label: this.props.translate('profilePage.selectYourPronouns'), }} - defaultValue={pronounsPickerValue} + value={pronounsPickerValue} /> {this.state.hasSelfSelectedPronouns && ( @@ -290,7 +290,6 @@ class ProfilePage extends Component { label={this.props.translate('profilePage.timezone')} items={timezones} isDisabled={this.state.isAutomaticTimezone} - defaultValue={this.state.selectedTimezone} value={this.state.selectedTimezone} /> diff --git a/src/styles/styles.js b/src/styles/styles.js index 467a5609a4bf..65bd0cfc3f1a 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2671,6 +2671,10 @@ const styles = { fontFamily: fontFamily.GTA, marginLeft: 6, }, + + translate0InvertY: { + transform: 'translate3d(0,0,0) scaleY(-1)', + }, }; export default styles;