diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js
--- a/web/input/input-state-container.react.js
+++ b/web/input/input-state-container.react.js
@@ -118,11 +118,13 @@
     [threadID: string]: { [localUploadID: string]: PendingMultimediaUpload },
   },
   +drafts: { [threadID: string]: string },
+  +textCursorPositions: { [threadID: string]: number },
 };
 class InputStateContainer extends React.PureComponent<Props, State> {
   state: State = {
     pendingUploads: {},
     drafts: {},
+    textCursorPositions: {},
   };
   replyCallbacks: Array<(message: string) => void> = [];
   pendingThreadCreations = new Map<string, Promise<string>>();
@@ -153,8 +155,12 @@
       state.pendingUploads,
       props,
     );
+    const textCursorPositions = InputStateContainer.reassignToRealizedThreads(
+      state.textCursorPositions,
+      props,
+    );
 
-    if (!drafts && !pendingUploads) {
+    if (!drafts && !pendingUploads && !textCursorPositions) {
       return null;
     }
 
@@ -165,6 +171,9 @@
     if (pendingUploads) {
       stateUpdate.pendingUploads = pendingUploads;
     }
+    if (textCursorPositions) {
+      stateUpdate.textCursorPositions = textCursorPositions;
+    }
     return stateUpdate;
   }
 
@@ -451,9 +460,11 @@
     createSelector(
       (state: State) => state.pendingUploads[threadID],
       (state: State) => state.drafts[threadID],
+      (state: State) => state.textCursorPositions[threadID],
       (
         pendingUploads: ?{ [localUploadID: string]: PendingMultimediaUpload },
         draft: ?string,
+        textCursorPosition: ?number,
       ) => {
         let threadPendingUploads = [];
         const assignedUploads = {};
@@ -473,7 +484,8 @@
         return {
           pendingUploads: threadPendingUploads,
           assignedUploads,
-          draft: draft ? draft : '',
+          draft: draft ?? '',
+          textCursorPosition: textCursorPosition ?? 0,
           appendFiles: (files: $ReadOnlyArray<File>) =>
             this.appendFiles(threadID, files),
           cancelPendingUpload: (localUploadID: string) =>
@@ -485,6 +497,8 @@
           createMultimediaMessage: (localID: number, threadInfo: ThreadInfo) =>
             this.createMultimediaMessage(localID, threadInfo),
           setDraft: (newDraft: string) => this.setDraft(threadID, newDraft),
+          setTextCursorPosition: (newPosition: number) =>
+            this.setTextCursorPosition(threadID, newPosition),
           messageHasUploadFailure: (localMessageID: string) =>
             this.messageHasUploadFailure(assignedUploads[localMessageID]),
           retryMultimediaMessage: (
@@ -1066,6 +1080,18 @@
     });
   }
 
+  setTextCursorPosition(threadID: string, newPosition: number) {
+    this.setState(prevState => {
+      const newThreadID = this.getRealizedOrPendingThreadID(threadID);
+      return {
+        textCursorPositions: {
+          ...prevState.textCursorPositions,
+          [newThreadID]: newPosition,
+        },
+      };
+    });
+  }
+
   setProgress(
     threadID: string,
     localUploadID: string,
diff --git a/web/input/input-state.js b/web/input/input-state.js
--- a/web/input/input-state.js
+++ b/web/input/input-state.js
@@ -36,29 +36,31 @@
 
 // This type represents the input state for a particular thread
 export type InputState = {
-  pendingUploads: $ReadOnlyArray<PendingMultimediaUpload>,
-  assignedUploads: {
+  +pendingUploads: $ReadOnlyArray<PendingMultimediaUpload>,
+  +assignedUploads: {
     [messageID: string]: $ReadOnlyArray<PendingMultimediaUpload>,
   },
-  draft: string,
-  appendFiles: (files: $ReadOnlyArray<File>) => Promise<boolean>,
-  cancelPendingUpload: (localUploadID: string) => void,
-  sendTextMessage: (
+  +draft: string,
+  +textCursorPosition: number,
+  +appendFiles: (files: $ReadOnlyArray<File>) => Promise<boolean>,
+  +cancelPendingUpload: (localUploadID: string) => void,
+  +sendTextMessage: (
     messageInfo: RawTextMessageInfo,
     threadInfo: ThreadInfo,
   ) => Promise<void>,
-  createMultimediaMessage: (localID: number, threadInfo: ThreadInfo) => void,
-  setDraft: (draft: string) => void,
-  messageHasUploadFailure: (localMessageID: string) => boolean,
-  retryMultimediaMessage: (
+  +createMultimediaMessage: (localID: number, threadInfo: ThreadInfo) => void,
+  +setDraft: (draft: string) => void,
+  +setTextCursorPosition: (newPosition: number) => void,
+  +messageHasUploadFailure: (localMessageID: string) => boolean,
+  +retryMultimediaMessage: (
     localMessageID: string,
     threadInfo: ThreadInfo,
   ) => void,
-  addReply: (text: string) => void,
-  addReplyListener: ((message: string) => void) => void,
-  removeReplyListener: ((message: string) => void) => void,
-  registerSendCallback: (() => mixed) => void,
-  unregisterSendCallback: (() => mixed) => void,
+  +addReply: (text: string) => void,
+  +addReplyListener: ((message: string) => void) => void,
+  +removeReplyListener: ((message: string) => void) => void,
+  +registerSendCallback: (() => mixed) => void,
+  +unregisterSendCallback: (() => mixed) => void,
 };
 
 const InputStateContext: React.Context<?InputState> = React.createContext<?InputState>(