diff --git a/lib/selectors/chat-selectors.js b/lib/selectors/chat-selectors.js
--- a/lib/selectors/chat-selectors.js
+++ b/lib/selectors/chat-selectors.js
@@ -292,6 +292,7 @@
       +threadCreatedFromMessage: ?ThreadInfo,
       +reactions: ReactionInfo,
       +hasBeenEdited: boolean,
+      +isPinned: boolean,
     };
 export type ChatMessageItem = { itemType: 'loader' } | ChatMessageInfoItem;
 
@@ -377,6 +378,24 @@
     targetMessageEditMap.set(messageInfo.targetMessageID, messageInfo.text);
   }
 
+  const targetMessagePinStatusMap = new Map<string, boolean>();
+  // Once again, we iterate backwards to put the order of messages in
+  // chronological order (i.e. oldest to newest) to handle pinned messages.
+  // This is important because we want to make sure that the most recent pin
+  // action is the one that is used to determine whether a message
+  // is pinned or not.
+  for (let i = messages.length - 1; i >= 0; i--) {
+    const messageInfo = messages[i];
+    if (messageInfo.type !== messageTypes.TOGGLE_PIN) {
+      continue;
+    }
+
+    targetMessagePinStatusMap.set(
+      messageInfo.targetMessageID,
+      messageInfo.action === 'pin',
+    );
+  }
+
   const chatMessageItems = [];
   let lastMessageInfo = null;
   for (let i = messages.length - 1; i >= 0; i--) {
@@ -438,6 +457,11 @@
         ? threadInfoFromSourceMessageID[messageInfo.id]
         : undefined;
 
+    const isPinned = !!(
+      originalMessageInfo.id &&
+      targetMessagePinStatusMap.get(originalMessageInfo.id)
+    );
+
     const renderedReactions: ReactionInfo = (() => {
       const result = {};
 
@@ -496,6 +520,7 @@
         threadCreatedFromMessage,
         reactions: renderedReactions,
         hasBeenEdited,
+        isPinned,
       });
     } else {
       invariant(