diff --git a/lib/reducers/message-reducer.js b/lib/reducers/message-reducer.js
--- a/lib/reducers/message-reducer.js
+++ b/lib/reducers/message-reducer.js
@@ -1029,27 +1029,33 @@
     const { localID, threadID } = payload;
     invariant(localID, `localID should be set on ${action.type}`);
 
+    const messageIDs = messageStore.threads[threadID]?.messageIDs ?? [];
+    if (!messageStore.messages[localID]) {
+      for (const existingMessageID of messageIDs) {
+        const existingMessageInfo = messageStore.messages[existingMessageID];
+        if (existingMessageInfo && existingMessageInfo.localID === localID) {
+          return { messageStoreOperations: [], messageStore };
+        }
+      }
+    }
+
     const messageStoreOperations = [
       {
         type: 'replace',
         payload: { id: localID, messageInfo: payload },
       },
     ];
-    const processedMessageStore = processMessageStoreOperations(
-      messageStore,
-      messageStoreOperations,
-    );
 
     const now = Date.now();
-    const messageIDs = messageStore.threads[threadID]?.messageIDs ?? [];
+    let updatedThreads;
+    let local = { ...messageStore.local };
     if (messageStore.messages[localID]) {
       const messages = { ...messageStore.messages, [localID]: payload };
-      const local = _pickBy(
+      local = _pickBy(
         (localInfo: LocalMessageInfo, key: string) => key !== localID,
       )(messageStore.local);
       const thread = messageStore.threads[threadID];
-      const threads = {
-        ...messageStore.threads,
+      updatedThreads = {
         [threadID]: {
           messageIDs: sortMessageIDs(messages)(messageIDs),
           startReached: thread?.startReached ?? true,
@@ -1057,47 +1063,54 @@
           lastPruned: thread?.lastPruned ?? now,
         },
       };
-      return {
-        messageStoreOperations,
-        messageStore: {
-          ...messageStore,
-          messages: processedMessageStore.messages,
-          threads,
-          local,
-        },
+    } else {
+      updatedThreads = {
+        [threadID]: messageStore.threads[threadID]
+          ? {
+              ...messageStore.threads[threadID],
+              messageIDs: [localID, ...messageIDs],
+            }
+          : {
+              messageIDs: [localID],
+              startReached: true,
+              lastNavigatedTo: now,
+              lastPruned: now,
+            },
       };
     }
 
-    for (const existingMessageID of messageIDs) {
-      const existingMessageInfo = messageStore.messages[existingMessageID];
-      if (existingMessageInfo && existingMessageInfo.localID === localID) {
-        return { messageStoreOperations: [], messageStore };
-      }
-    }
+    const threads = {
+      ...messageStore.threads,
+      ...updatedThreads,
+    };
+    messageStoreOperations.push({
+      type: 'replace_threads',
+      payload: {
+        threads: { ...updatedThreads },
+      },
+    });
 
-    const threadState: ThreadMessageInfo = messageStore.threads[threadID]
-      ? {
-          ...messageStore.threads[threadID],
-          messageIDs: [localID, ...messageIDs],
-        }
-      : {
-          messageIDs: [localID],
-          startReached: true,
-          lastNavigatedTo: now,
-          lastPruned: now,
-        };
+    const processedMessageStore = processMessageStoreOperations(
+      messageStore,
+      messageStoreOperations,
+    );
+
+    const newMessageStore = {
+      messages: processedMessageStore.messages,
+      threads,
+      local,
+      currentAsOf: messageStore.currentAsOf,
+    };
+
+    assertMessageStoreThreadsAreEqual(
+      processedMessageStore,
+      newMessageStore,
+      action.type,
+    );
 
     return {
       messageStoreOperations,
-      messageStore: {
-        messages: processedMessageStore.messages,
-        threads: {
-          ...messageStore.threads,
-          [threadID]: threadState,
-        },
-        local: messageStore.local,
-        currentAsOf: messageStore.currentAsOf,
-      },
+      messageStore: newMessageStore,
     };
   } else if (
     action.type === sendTextMessageActionTypes.failed ||