diff --git a/web/chat/composed-message.react.js b/web/chat/composed-message.react.js
--- a/web/chat/composed-message.react.js
+++ b/web/chat/composed-message.react.js
@@ -47,36 +47,41 @@
   tooltipPositions.TOP,
 ];
 
-type BaseProps = {
+type Props = {
   +item: ChatMessageInfoItem,
   +threadInfo: ThreadInfo,
   +shouldDisplayPinIndicator: boolean,
   +sendFailed: boolean,
   +children: React.Node,
   +fixedWidth?: boolean,
-  +borderRadius: number,
-};
-type DefaultProps = { +borderRadius: number };
-type BaseConfig = React.Config<BaseProps, DefaultProps>;
-type Props = {
-  ...BaseProps,
-  +onMouseLeave: ?() => mixed,
-  +onMouseEnter: (event: SyntheticEvent<HTMLDivElement>) => mixed,
-  +stringForUser: ?string,
-  +onClickUser: () => mixed,
+  +borderRadius?: number,
 };
-class ComposedMessage extends React.PureComponent<Props> {
-  static defaultProps: DefaultProps = { borderRadius: 8 };
+const ComposedMessage: React.ComponentType<Props> = React.memo<Props>(
+  function ComposedMessage(props) {
+    const { item, threadInfo } = props;
+    const { creator } = item.messageInfo;
+    const { isViewer } = creator;
+    const availablePositions = isViewer
+      ? availableTooltipPositionsForViewerMessage
+      : availableTooltipPositionsForNonViewerMessage;
+
+    const { onMouseLeave, onMouseEnter } = useMessageTooltip({
+      item,
+      threadInfo,
+      availablePositions,
+    });
+
+    const shouldShowUsername = !isViewer && item.startsCluster;
+
+    const pushUserProfileModal = usePushUserProfileModal(creator.id);
 
-  render(): React.Node {
-    assertComposableMessageType(this.props.item.messageInfo.type);
-    const { borderRadius, item, threadInfo, shouldDisplayPinIndicator } =
-      this.props;
+    assertComposableMessageType(item.messageInfo.type);
+    const { shouldDisplayPinIndicator } = props;
+    const borderRadius = props.borderRadius ?? 8;
     const { hasBeenEdited, isPinned } = item;
-    const { id, creator } = item.messageInfo;
+    const { id } = item.messageInfo;
     const threadColor = threadInfo.color;
 
-    const { isViewer } = creator;
     const contentClassName = classNames({
       [css.content]: true,
       [css.viewerContent]: isViewer,
@@ -84,11 +89,11 @@
     });
     const messageBoxContainerClassName = classNames({
       [css.messageBoxContainer]: true,
-      [css.fixedWidthMessageBoxContainer]: this.props.fixedWidth,
+      [css.fixedWidthMessageBoxContainer]: props.fixedWidth,
     });
     const messageBoxClassName = classNames({
       [css.messageBox]: true,
-      [css.fixedWidthMessageBox]: this.props.fixedWidth,
+      [css.fixedWidthMessageBox]: props.fixedWidth,
     });
     const messageBoxStyle = {
       borderTopRightRadius: isViewer && !item.startsCluster ? 0 : borderRadius,
@@ -98,10 +103,10 @@
     };
 
     let authorName = null;
-    const { stringForUser } = this.props;
+    const stringForUser = useStringForUser(shouldShowUsername ? creator : null);
     if (stringForUser) {
       authorName = (
-        <span className={css.authorName} onClick={this.props.onClickUser}>
+        <span className={css.authorName} onClick={pushUserProfileModal}>
           {stringForUser}
         </span>
       );
@@ -121,7 +126,7 @@
         notDeliveredP2PMessages.length === 0
       ) {
         deliveryIconSpan = <CheckCircleIcon />;
-      } else if (this.props.sendFailed) {
+      } else if (props.sendFailed) {
         deliveryIconSpan = <XCircleIcon />;
         deliveryIconColor = 'FF0000';
         failedSendInfo = <FailedSend item={item} threadInfo={threadInfo} />;
@@ -163,7 +168,7 @@
     let avatar;
     if (!isViewer && item.endsCluster) {
       avatar = (
-        <div className={css.avatarContainer} onClick={this.props.onClickUser}>
+        <div className={css.avatarContainer} onClick={pushUserProfileModal}>
           <UserAvatar size="S" userID={creator.id} />
         </div>
       );
@@ -197,8 +202,8 @@
           {avatar}
           <div
             className={messageBoxContainerClassName}
-            onMouseEnter={this.props.onMouseEnter}
-            onMouseLeave={this.props.onMouseLeave}
+            onMouseEnter={onMouseEnter}
+            onMouseLeave={onMouseLeave}
           >
             {pinIcon}
             <div
@@ -206,7 +211,7 @@
               style={messageBoxStyle}
               id={getComposedMessageID(item.messageInfo)}
             >
-              {this.props.children}
+              {props.children}
             </div>
           </div>
           {deliveryIcon}
@@ -215,42 +220,7 @@
         {inlineEngagement}
       </React.Fragment>
     );
-  }
-}
-
-type ConnectedConfig = React.Config<
-  BaseProps,
-  typeof ComposedMessage.defaultProps,
->;
-const ConnectedComposedMessage: React.ComponentType<ConnectedConfig> =
-  React.memo<BaseConfig>(function ConnectedComposedMessage(props) {
-    const { item, threadInfo } = props;
-    const { creator } = props.item.messageInfo;
-    const { isViewer } = creator;
-    const availablePositions = isViewer
-      ? availableTooltipPositionsForViewerMessage
-      : availableTooltipPositionsForNonViewerMessage;
-
-    const { onMouseLeave, onMouseEnter } = useMessageTooltip({
-      item,
-      threadInfo,
-      availablePositions,
-    });
-
-    const shouldShowUsername = !isViewer && item.startsCluster;
-    const stringForUser = useStringForUser(shouldShowUsername ? creator : null);
-
-    const pushUserProfileModal = usePushUserProfileModal(creator.id);
-
-    return (
-      <ComposedMessage
-        {...props}
-        onMouseLeave={onMouseLeave}
-        onMouseEnter={onMouseEnter}
-        stringForUser={stringForUser}
-        onClickUser={pushUserProfileModal}
-      />
-    );
-  });
+  },
+);
 
-export default ConnectedComposedMessage;
+export default ComposedMessage;