diff --git a/src/components/RoomNameInput.js b/src/components/RoomNameInput.js
deleted file mode 100644
index 95e016cd6a18..000000000000
--- a/src/components/RoomNameInput.js
+++ /dev/null
@@ -1,94 +0,0 @@
-import React, {Component} from 'react';
-import PropTypes from 'prop-types';
-import CONST from '../CONST';
-import withLocalize, {withLocalizePropTypes} from './withLocalize';
-import TextInput from './TextInput';
-
-const propTypes = {
- /** Callback to execute when the text input is modified correctly */
- onChangeText: PropTypes.func,
-
- /** 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,
-
- /** Error text to show */
- errorText: PropTypes.string,
-
- ...withLocalizePropTypes,
-
- /** A ref forwarded to the TextInput */
- forwardedRef: PropTypes.func,
-};
-
-const defaultProps = {
- onChangeText: () => {},
- value: '',
- disabled: false,
- errorText: '',
- forwardedRef: () => {},
-};
-
-class RoomNameInput extends Component {
- constructor(props) {
- super(props);
-
- this.setModifiedRoomName = this.setModifiedRoomName.bind(this);
- }
-
- /**
- * Calls the onChangeText callback with a modified room name
- * @param {Event} event
- */
- setModifiedRoomName(event) {
- const roomName = event.nativeEvent.text;
- const modifiedRoomName = this.modifyRoomName(roomName);
- this.props.onChangeText(modifiedRoomName);
- }
-
- /**
- * Modifies the room name to follow our conventions:
- * - Max length 80 characters
- * - Cannot not include space or special characters, and we automatically apply an underscore for spaces
- * - Must be lowercase
- * @param {String} roomName
- * @returns {String}
- */
- modifyRoomName(roomName) {
- const modifiedRoomNameWithoutHash = roomName
- .replace(/ /g, '_')
- .replace(/[^a-zA-Z\d_]/g, '')
- .substr(0, CONST.REPORT.MAX_ROOM_NAME_LENGTH)
- .toLowerCase();
-
- return `${CONST.POLICY.ROOM_PREFIX}${modifiedRoomNameWithoutHash}`;
- }
-
- render() {
- return (
-
- );
- }
-}
-
-RoomNameInput.propTypes = propTypes;
-RoomNameInput.defaultProps = defaultProps;
-
-export default withLocalize(
- React.forwardRef((props, ref) => (
- // eslint-disable-next-line react/jsx-props-no-spreading
-
- )),
-);
diff --git a/src/components/RoomNameInput/index.js b/src/components/RoomNameInput/index.js
new file mode 100644
index 000000000000..6c015ec361de
--- /dev/null
+++ b/src/components/RoomNameInput/index.js
@@ -0,0 +1,81 @@
+import React, {Component} from 'react';
+import CONST from '../../CONST';
+import withLocalize from '../withLocalize';
+import TextInput from '../TextInput';
+import * as roomNameInputPropTypes from './roomNameInputPropTypes';
+import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils';
+
+class RoomNameInput extends Component {
+ constructor(props) {
+ super(props);
+
+ this.setModifiedRoomName = this.setModifiedRoomName.bind(this);
+ this.setSelection = this.setSelection.bind(this);
+
+ this.state = {
+ selection: undefined,
+ };
+ }
+
+ /**
+ * Calls the onChangeText callback with a modified room name
+ * @param {Event} event
+ */
+ setModifiedRoomName(event) {
+ const roomName = event.nativeEvent.text;
+ const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName);
+ this.props.onChangeText(modifiedRoomName);
+
+ // Prevent cursor jump behaviour:
+ // Check if newRoomNameWithHash is the same as modifiedRoomName
+ // If it is then the room name is valid (does not contain unallowed characters); no action required
+ // If not then the room name contains unvalid characters and we must adjust the cursor position manually
+ // Read more: https://github.com/Expensify/App/issues/12741
+ const oldRoomNameWithHash = this.props.value || '';
+ const newRoomNameWithHash = `${CONST.POLICY.ROOM_PREFIX}${roomName}`;
+ if (modifiedRoomName !== newRoomNameWithHash) {
+ const offset = modifiedRoomName.length - oldRoomNameWithHash.length;
+ const selection = {
+ start: this.state.selection.start + offset,
+ end: this.state.selection.end + offset,
+ };
+ this.setSelection(selection);
+ }
+ }
+
+ /**
+ * Set the selection
+ * @param {Object} selection
+ */
+ setSelection(selection) {
+ this.setState({selection});
+ }
+
+ render() {
+ return (
+ this.setSelection(event.nativeEvent.selection)}
+ errorText={this.props.errorText}
+ autoCapitalize="none"
+ />
+ );
+ }
+}
+
+RoomNameInput.propTypes = roomNameInputPropTypes.propTypes;
+RoomNameInput.defaultProps = roomNameInputPropTypes.defaultProps;
+
+export default withLocalize(
+ React.forwardRef((props, ref) => (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+ )),
+);
diff --git a/src/components/RoomNameInput/index.native.js b/src/components/RoomNameInput/index.native.js
new file mode 100644
index 000000000000..2eb22be46e18
--- /dev/null
+++ b/src/components/RoomNameInput/index.native.js
@@ -0,0 +1,50 @@
+import React, {Component} from 'react';
+import CONST from '../../CONST';
+import withLocalize from '../withLocalize';
+import TextInput from '../TextInput';
+import * as roomNameInputPropTypes from './roomNameInputPropTypes';
+import * as RoomNameInputUtils from '../../libs/RoomNameInputUtils';
+
+class RoomNameInput extends Component {
+ constructor(props) {
+ super(props);
+
+ this.setModifiedRoomName = this.setModifiedRoomName.bind(this);
+ }
+
+ /**
+ * Calls the onChangeText callback with a modified room name
+ * @param {Event} event
+ */
+ setModifiedRoomName(event) {
+ const roomName = event.nativeEvent.text;
+ const modifiedRoomName = RoomNameInputUtils.modifyRoomName(roomName);
+ this.props.onChangeText(modifiedRoomName);
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+RoomNameInput.propTypes = roomNameInputPropTypes.propTypes;
+RoomNameInput.defaultProps = roomNameInputPropTypes.defaultProps;
+
+export default withLocalize(
+ React.forwardRef((props, ref) => (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+ )),
+);
diff --git a/src/components/RoomNameInput/roomNameInputPropTypes.js b/src/components/RoomNameInput/roomNameInputPropTypes.js
new file mode 100644
index 000000000000..db5ce4b3c0c6
--- /dev/null
+++ b/src/components/RoomNameInput/roomNameInputPropTypes.js
@@ -0,0 +1,31 @@
+import PropTypes from 'prop-types';
+import {withLocalizePropTypes} from '../withLocalize';
+
+const propTypes = {
+ /** Callback to execute when the text input is modified correctly */
+ onChangeText: PropTypes.func,
+
+ /** 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,
+
+ /** Error text to show */
+ errorText: PropTypes.string,
+
+ ...withLocalizePropTypes,
+
+ /** A ref forwarded to the TextInput */
+ forwardedRef: PropTypes.func,
+};
+
+const defaultProps = {
+ onChangeText: () => {},
+ value: '',
+ disabled: false,
+ errorText: '',
+ forwardedRef: () => {},
+};
+
+export {propTypes, defaultProps};
diff --git a/src/libs/RoomNameInputUtils.js b/src/libs/RoomNameInputUtils.js
new file mode 100644
index 000000000000..5522096973de
--- /dev/null
+++ b/src/libs/RoomNameInputUtils.js
@@ -0,0 +1,24 @@
+import CONST from '../CONST';
+
+/**
+ * Modifies the room name to follow our conventions:
+ * - Max length 80 characters
+ * - Cannot not include space or special characters, and we automatically apply an underscore for spaces
+ * - Must be lowercase
+ * @param {String} roomName
+ * @returns {String}
+ */
+function modifyRoomName(roomName) {
+ const modifiedRoomNameWithoutHash = roomName
+ .replace(/ /g, '_')
+ .replace(/[^a-zA-Z\d_]/g, '')
+ .substr(0, CONST.REPORT.MAX_ROOM_NAME_LENGTH)
+ .toLowerCase();
+
+ return `${CONST.POLICY.ROOM_PREFIX}${modifiedRoomNameWithoutHash}`;
+}
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ modifyRoomName,
+};