From fab9c8e6ed313751ec28fcd8bb15b8aeba0ccc1d Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Wed, 17 Jan 2024 15:37:20 +0100
Subject: [PATCH 01/29] fix: wip

---
 src/libs/actions/EmojiPickerAction.ts         |   2 +-
 src/libs/actions/User.ts                      |   6 +-
 ...tionCompose.js => ReportActionCompose.tsx} | 153 ++++++++++--------
 3 files changed, 88 insertions(+), 73 deletions(-)
 rename src/pages/home/report/ReportActionCompose/{ReportActionCompose.js => ReportActionCompose.tsx} (81%)

diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts
index 56a5f34c0b8e..3a4da86dcbb9 100644
--- a/src/libs/actions/EmojiPickerAction.ts
+++ b/src/libs/actions/EmojiPickerAction.ts
@@ -66,7 +66,7 @@ function showEmojiPicker(
 /**
  * Hide the Emoji Picker modal.
  */
-function hideEmojiPicker(isNavigating: boolean) {
+function hideEmojiPicker(isNavigating?: boolean) {
     if (!emojiPickerRef.current) {
         return;
     }
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index 8e3bd5f2c017..e0f3003ed9e8 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -13,7 +13,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
-import type {FrequentlyUsedEmoji} from '@src/types/onyx';
+import type {BlockedFromConcierge, FrequentlyUsedEmoji} from '@src/types/onyx';
 import type Login from '@src/types/onyx/Login';
 import type {OnyxServerUpdate} from '@src/types/onyx/OnyxUpdatesFromServer';
 import type OnyxPersonalDetails from '@src/types/onyx/PersonalDetails';
@@ -27,8 +27,6 @@ import * as PersonalDetails from './PersonalDetails';
 import * as Report from './Report';
 import * as Session from './Session';
 
-type BlockedFromConciergeNVP = {expiresAt: number};
-
 let currentUserAccountID = -1;
 let currentEmail = '';
 Onyx.connect({
@@ -445,7 +443,7 @@ function validateSecondaryLogin(contactMethod: string, validateCode: string) {
  * and if so whether the expiresAt date of a user's ban is before right now
  *
  */
-function isBlockedFromConcierge(blockedFromConciergeNVP: OnyxEntry<BlockedFromConciergeNVP>): boolean {
+function isBlockedFromConcierge(blockedFromConciergeNVP: OnyxEntry<BlockedFromConcierge>): boolean {
     if (isEmptyObject(blockedFromConciergeNVP)) {
         return false;
     }
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
similarity index 81%
rename from src/pages/home/report/ReportActionCompose/ReportActionCompose.js
rename to src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index c072666920ae..1f0574f577cb 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -1,21 +1,23 @@
 import {PortalHost} from '@gorhom/portal';
 import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
+import type {SyntheticEvent} from 'react';
 import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import {View} from 'react-native';
+import {MeasureInWindowOnSuccessCallback, View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
 import {runOnJS, setNativeProps, useAnimatedRef} from 'react-native-reanimated';
-import _ from 'underscore';
 import AttachmentModal from '@components/AttachmentModal';
 import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton';
 import ExceededCommentLength from '@components/ExceededCommentLength';
 import OfflineIndicator from '@components/OfflineIndicator';
 import OfflineWithFeedback from '@components/OfflineWithFeedback';
-import {usePersonalDetails, withNetwork} from '@components/OnyxProvider';
-import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
+import {usePersonalDetails} from '@components/OnyxProvider';
+import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
+import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
 import useDebounce from '@hooks/useDebounce';
 import useHandleExceedMaxCommentLength from '@hooks/useHandleExceedMaxCommentLength';
 import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWindowDimensions from '@hooks/useWindowDimensions';
 import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
@@ -26,69 +28,88 @@ import getModalState from '@libs/getModalState';
 import * as ReportUtils from '@libs/ReportUtils';
 import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside';
 import ParticipantLocalTime from '@pages/home/report/ParticipantLocalTime';
-import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
 import ReportDropUI from '@pages/home/report/ReportDropUI';
 import ReportTypingIndicator from '@pages/home/report/ReportTypingIndicator';
-import reportPropTypes from '@pages/reportPropTypes';
 import * as EmojiPickerActions from '@userActions/EmojiPickerAction';
 import * as Report from '@userActions/Report';
 import * as User from '@userActions/User';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import type * as OnyxTypes from '@src/types/onyx';
+import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
 import AttachmentPickerWithMenuItems from './AttachmentPickerWithMenuItems';
 import ComposerWithSuggestions from './ComposerWithSuggestions';
 import SendButton from './SendButton';
 
-const propTypes = {
+type ComposerRef = {
+    blur: () => void;
+    focus: (shouldDelay?: boolean) => void;
+    replaceSelectionWithText: (text: string, shouldAddTrailSpace: boolean) => void;
+    prepareCommentAndResetComposer: () => string;
+    isFocused: () => boolean;
+};
+
+type SuggestionsRef = {
+    resetSuggestions: () => void;
+    onSelectionChange: (event: any) => void;
+    triggerHotkeyActions: (event: any) => void;
+    updateShouldShowSuggestionMenuToFalse: () => void;
+    setShouldBlockSuggestionCalc: (shouldBlock: boolean) => void;
+    getSuggestions: () => string[];
+};
+
+type ReportActionComposeOnyxProps = {
+    /** The NVP describing a user's block status */
+    blockedFromConcierge: OnyxEntry<OnyxTypes.BlockedFromConcierge>;
+
+    /** Whether the composer input should be shown */
+    shouldShowComposeInput: OnyxEntry<boolean>;
+};
+
+type ReportActionComposeProps = {
     /** A method to call when the form is submitted */
-    onSubmit: PropTypes.func.isRequired,
+    onSubmit: (newComment: string | undefined) => void;
 
     /** The ID of the report actions will be created for */
-    reportID: PropTypes.string.isRequired,
+    reportID: string;
 
     /** Array of report actions for this report */
-    reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)),
+    reportActions?: OnyxTypes.ReportAction[];
 
     /** The report currently being looked at */
-    report: reportPropTypes,
+    report: OnyxEntry<OnyxTypes.Report>;
 
     /** Is composer full size */
-    isComposerFullSize: PropTypes.bool,
+    isComposerFullSize?: boolean;
 
     /** Whether user interactions should be disabled */
-    disabled: PropTypes.bool,
+    disabled?: boolean;
 
     /** Height of the list which the composer is part of */
-    listHeight: PropTypes.number,
-
-    // The NVP describing a user's block status
-    blockedFromConcierge: PropTypes.shape({
-        // The date that the user will be unblocked
-        expiresAt: PropTypes.string,
-    }),
+    listHeight?: number;
 
     /** Whether the composer input should be shown */
-    shouldShowComposeInput: PropTypes.bool,
+    shouldShowComposeInput?: boolean;
 
     /** The type of action that's pending  */
-    pendingAction: PropTypes.oneOf(['add', 'update', 'delete']),
+    pendingAction?: OnyxCommon.PendingAction;
 
     /** /** Whetjer the report is ready for display */
-    isReportReadyForDisplay: PropTypes.bool,
-    ...withCurrentUserPersonalDetailsPropTypes,
-};
-
-const defaultProps = {
-    report: {},
-    blockedFromConcierge: {},
-    preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE,
-    isComposerFullSize: false,
-    pendingAction: null,
-    shouldShowComposeInput: true,
-    listHeight: 0,
-    isReportReadyForDisplay: true,
-    ...withCurrentUserPersonalDetailsDefaultProps,
-};
+    isReportReadyForDisplay?: boolean;
+} & ReportActionComposeOnyxProps &
+    WithCurrentUserPersonalDetailsProps;
+
+// const defaultProps = {
+//     report: {},
+//     blockedFromConcierge: {},
+//     preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE,
+//     isComposerFullSize: false,
+//     pendingAction: null,
+//     shouldShowComposeInput: true,
+//     listHeight: 0,
+//     isReportReadyForDisplay: true,
+//     ...withCurrentUserPersonalDetailsDefaultProps,
+// };
 
 // We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will
 // prevent auto focus on existing chat for mobile device
@@ -101,7 +122,6 @@ function ReportActionCompose({
     currentUserPersonalDetails,
     disabled,
     isComposerFullSize,
-    network,
     onSubmit,
     pendingAction,
     report,
@@ -110,10 +130,11 @@ function ReportActionCompose({
     listHeight,
     shouldShowComposeInput,
     isReportReadyForDisplay,
-}) {
+}: ReportActionComposeProps) {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     const {isMediumScreenWidth, isSmallScreenWidth} = useWindowDimensions();
+    const {isOffline} = useNetwork();
     const animatedRef = useAnimatedRef();
     const actionButtonRef = useRef(null);
     const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
@@ -122,7 +143,7 @@ function ReportActionCompose({
      */
     const [isFocused, setIsFocused] = useState(() => {
         const initialModalState = getModalState();
-        return shouldFocusInputOnScreenFocus && shouldShowComposeInput && !initialModalState.isVisible && !initialModalState.willAlertModalBecomeVisible;
+        return shouldFocusInputOnScreenFocus && shouldShowComposeInput && !initialModalState?.isVisible && !initialModalState?.willAlertModalBecomeVisible;
     });
     const [isFullComposerAvailable, setIsFullComposerAvailable] = useState(isComposerFullSize);
 
@@ -167,11 +188,11 @@ function ReportActionCompose({
      */
     const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength();
 
-    const suggestionsRef = useRef(null);
-    const composerRef = useRef(null);
+    const suggestionsRef = useRef<SuggestionsRef>(null);
+    const composerRef = useRef<ComposerRef>(null);
 
     const reportParticipantIDs = useMemo(
-        () => _.without(lodashGet(report, 'participantAccountIDs', []), currentUserPersonalDetails.accountID),
+        () => report?.participantAccountIDs?.filter((accountID) => accountID !== currentUserPersonalDetails.accountID),
         [currentUserPersonalDetails.accountID, report],
     );
 
@@ -184,7 +205,7 @@ function ReportActionCompose({
 
     // If we are on a small width device then don't show last 3 items from conciergePlaceholderOptions
     const conciergePlaceholderRandomIndex = useMemo(
-        () => _.random(translate('reportActionCompose.conciergePlaceholderOptions').length - (isSmallScreenWidth ? 4 : 1)),
+        () => Math.floor(Math.random() * (translate('reportActionCompose.conciergePlaceholderOptions').length - (isSmallScreenWidth ? 4 : 1) + 1)),
         // eslint-disable-next-line react-hooks/exhaustive-deps
         [],
     );
@@ -203,7 +224,7 @@ function ReportActionCompose({
     }, [report, blockedFromConcierge, translate, conciergePlaceholderRandomIndex]);
 
     const focus = () => {
-        if (composerRef === null || composerRef.current === null) {
+        if (composerRef?.current === null) {
             return;
         }
         composerRef.current.focus(true);
@@ -219,9 +240,9 @@ function ReportActionCompose({
         isKeyboardVisibleWhenShowingModalRef.current = false;
     }, []);
 
-    const containerRef = useRef(null);
+    const containerRef = useRef<View>(null);
     const measureContainer = useCallback(
-        (callback) => {
+        (callback: MeasureInWindowOnSuccessCallback) => {
             if (!containerRef.current) {
                 return;
             }
@@ -234,9 +255,9 @@ function ReportActionCompose({
 
     const onAddActionPressed = useCallback(() => {
         if (!willBlurTextInputOnTapOutside) {
-            isKeyboardVisibleWhenShowingModalRef.current = composerRef.current.isFocused();
+            isKeyboardVisibleWhenShowingModalRef.current = !!composerRef.current?.isFocused();
         }
-        composerRef.current.blur();
+        composerRef.current?.blur();
     }, []);
 
     const onItemSelected = useCallback(() => {
@@ -250,12 +271,9 @@ function ReportActionCompose({
         suggestionsRef.current.updateShouldShowSuggestionMenuToFalse(false);
     }, []);
 
-    /**
-     * @param {Object} file
-     */
     const addAttachment = useCallback(
-        (file) => {
-            const newComment = composerRef.current.prepareCommentAndResetComposer();
+        (file: File) => {
+            const newComment = composerRef.current?.prepareCommentAndResetComposer();
             Report.addAttachment(reportID, file, newComment);
             setTextInputShouldClear(false);
         },
@@ -273,16 +291,14 @@ function ReportActionCompose({
 
     /**
      * Add a new comment to this chat
-     *
-     * @param {SyntheticEvent} [e]
      */
     const submitForm = useCallback(
-        (e) => {
+        (e?: SyntheticEvent) => {
             if (e) {
                 e.preventDefault();
             }
 
-            const newComment = composerRef.current.prepareCommentAndResetComposer();
+            const newComment = composerRef.current?.prepareCommentAndResetComposer();
             if (!newComment) {
                 return;
             }
@@ -323,7 +339,7 @@ function ReportActionCompose({
     // We are returning a callback here as we want to incoke the method on unmount only
     useEffect(
         () => () => {
-            if (!EmojiPickerActions.isActive(report.reportID)) {
+            if (!EmojiPickerActions.isActive(report?.reportID ?? '')) {
                 return;
             }
             EmojiPickerActions.hideEmojiPicker();
@@ -338,7 +354,7 @@ function ReportActionCompose({
 
     const hasReportRecipient = _.isObject(reportRecipient) && !_.isEmpty(reportRecipient);
 
-    const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || disabled || hasExceededMaxCommentLength;
+    const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || !!disabled || hasExceededMaxCommentLength;
 
     const handleSendMessage = useCallback(() => {
         'worklet';
@@ -355,7 +371,7 @@ function ReportActionCompose({
     }, [isSendDisabled, resetFullComposerSize, submitForm, animatedRef, isReportReadyForDisplay]);
 
     return (
-        <View style={[shouldShowReportRecipientLocalTime && !lodashGet(network, 'isOffline') && styles.chatItemComposeWithFirstRow, isComposerFullSize && styles.chatItemFullComposeRow]}>
+        <View style={[shouldShowReportRecipientLocalTime && !isOffline && styles.chatItemComposeWithFirstRow, isComposerFullSize && styles.chatItemFullComposeRow]}>
             <OfflineWithFeedback pendingAction={pendingAction}>
                 {shouldShowReportRecipientLocalTime && hasReportRecipient && <ParticipantLocalTime participant={reportRecipient} />}
             </OfflineWithFeedback>
@@ -376,12 +392,14 @@ function ReportActionCompose({
                             hasExceededMaxCommentLength && styles.borderColorDanger,
                         ]}
                     >
+                        {/* @ts-expect-error TODO: Remove this once AttachmentModal (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript. */}
                         <AttachmentModal
                             headerTitle={translate('reportActionCompose.sendAttachment')}
                             onConfirm={addAttachment}
                             onModalShow={() => setIsAttachmentPreviewActive(true)}
                             onModalHide={onAttachmentPreviewClose}
                         >
+                            {/* @ts-expect-error TODO: Remove this once AttachmentModal (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript. */}
                             {({displayFileInModal}) => (
                                 <>
                                     <AttachmentPickerWithMenuItems
@@ -408,6 +426,7 @@ function ReportActionCompose({
                                     />
                                     <ComposerWithSuggestions
                                         ref={composerRef}
+                                        // @ts-expect-error TODO: Remove this once ComposerWithSuggestions is migrated to TypeScript.
                                         animatedRef={animatedRef}
                                         suggestionsRef={suggestionsRef}
                                         isNextModalWillOpenRef={isNextModalWillOpenRef}
@@ -451,8 +470,9 @@ function ReportActionCompose({
                             <EmojiPickerButton
                                 isDisabled={isBlockedFromConcierge || disabled}
                                 onModalHide={focus}
-                                onEmojiSelected={(...args) => composerRef.current.replaceSelectionWithText(...args)}
-                                emojiPickerID={report.reportID}
+                                //  @ts-expect-error TODO: Remove this once EmojiPickerButton is migrated to TypeScript.
+                                onEmojiSelected={(...args) => composerRef.current?.replaceSelectionWithText(...args)}
+                                emojiPickerID={report?.reportID}
                             />
                         )}
                         <SendButton
@@ -465,7 +485,7 @@ function ReportActionCompose({
                             styles.flexRow,
                             styles.justifyContentBetween,
                             styles.alignItemsCenter,
-                            (!isSmallScreenWidth || (isSmallScreenWidth && !network.isOffline)) && styles.chatItemComposeSecondaryRow,
+                            (!isSmallScreenWidth || (isSmallScreenWidth && !isOffline)) && styles.chatItemComposeSecondaryRow,
                         ]}
                     >
                         {!isSmallScreenWidth && <OfflineIndicator containerStyles={[styles.chatItemComposeSecondaryRow]} />}
@@ -478,12 +498,9 @@ function ReportActionCompose({
     );
 }
 
-ReportActionCompose.propTypes = propTypes;
-ReportActionCompose.defaultProps = defaultProps;
 ReportActionCompose.displayName = 'ReportActionCompose';
 
 export default compose(
-    withNetwork(),
     withCurrentUserPersonalDetails,
     withOnyx({
         blockedFromConcierge: {

From 25f6646d0486eb0ce45714956e7118c65281ef31 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Thu, 18 Jan 2024 10:46:39 +0100
Subject: [PATCH 02/29] fix: finish working on migration

---
 .../ReportActionCompose.tsx                   | 52 +++++++------------
 1 file changed, 20 insertions(+), 32 deletions(-)

diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 1f0574f577cb..2cdb4e4d3602 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -1,8 +1,8 @@
 import {PortalHost} from '@gorhom/portal';
-import lodashGet from 'lodash/get';
 import type {SyntheticEvent} from 'react';
 import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import {MeasureInWindowOnSuccessCallback, View} from 'react-native';
+import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
+import {View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
 import {runOnJS, setNativeProps, useAnimatedRef} from 'react-native-reanimated';
@@ -21,7 +21,6 @@ import useNetwork from '@hooks/useNetwork';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWindowDimensions from '@hooks/useWindowDimensions';
 import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
-import compose from '@libs/compose';
 import getDraftComment from '@libs/ComposerUtils/getDraftComment';
 import * as DeviceCapabilities from '@libs/DeviceCapabilities';
 import getModalState from '@libs/getModalState';
@@ -37,6 +36,7 @@ import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type * as OnyxTypes from '@src/types/onyx';
 import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import AttachmentPickerWithMenuItems from './AttachmentPickerWithMenuItems';
 import ComposerWithSuggestions from './ComposerWithSuggestions';
 import SendButton from './SendButton';
@@ -51,9 +51,9 @@ type ComposerRef = {
 
 type SuggestionsRef = {
     resetSuggestions: () => void;
-    onSelectionChange: (event: any) => void;
-    triggerHotkeyActions: (event: any) => void;
-    updateShouldShowSuggestionMenuToFalse: () => void;
+    onSelectionChange: (event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void;
+    triggerHotkeyActions: (event: KeyboardEvent) => void;
+    updateShouldShowSuggestionMenuToFalse: (shouldShowSuggestionMenu: boolean) => void;
     setShouldBlockSuggestionCalc: (shouldBlock: boolean) => void;
     getSuggestions: () => string[];
 };
@@ -99,18 +99,6 @@ type ReportActionComposeProps = {
 } & ReportActionComposeOnyxProps &
     WithCurrentUserPersonalDetailsProps;
 
-// const defaultProps = {
-//     report: {},
-//     blockedFromConcierge: {},
-//     preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE,
-//     isComposerFullSize: false,
-//     pendingAction: null,
-//     shouldShowComposeInput: true,
-//     listHeight: 0,
-//     isReportReadyForDisplay: true,
-//     ...withCurrentUserPersonalDetailsDefaultProps,
-// };
-
 // We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will
 // prevent auto focus on existing chat for mobile device
 const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus();
@@ -119,17 +107,17 @@ const willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutsideFunc();
 
 function ReportActionCompose({
     blockedFromConcierge,
-    currentUserPersonalDetails,
+    currentUserPersonalDetails = {},
     disabled,
-    isComposerFullSize,
+    isComposerFullSize = false,
     onSubmit,
     pendingAction,
     report,
     reportID,
     reportActions,
-    listHeight,
-    shouldShowComposeInput,
-    isReportReadyForDisplay,
+    listHeight = 0,
+    shouldShowComposeInput = true,
+    isReportReadyForDisplay = true,
 }: ReportActionComposeProps) {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
@@ -313,7 +301,7 @@ function ReportActionCompose({
         isKeyboardVisibleWhenShowingModalRef.current = true;
     }, []);
 
-    const onBlur = useCallback((e) => {
+    const onBlur = useCallback((e: FocusEvent) => {
         setIsFocused(false);
         if (suggestionsRef.current) {
             suggestionsRef.current.resetSuggestions();
@@ -352,7 +340,7 @@ function ReportActionCompose({
     const reportRecipient = personalDetails[reportRecipientAcountIDs[0]];
     const shouldUseFocusedColor = !isBlockedFromConcierge && !disabled && isFocused;
 
-    const hasReportRecipient = _.isObject(reportRecipient) && !_.isEmpty(reportRecipient);
+    const hasReportRecipient = !isEmptyObject(reportRecipient);
 
     const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || !!disabled || hasExceededMaxCommentLength;
 
@@ -403,6 +391,7 @@ function ReportActionCompose({
                             {({displayFileInModal}) => (
                                 <>
                                     <AttachmentPickerWithMenuItems
+                                        // @ts-expect-error TODO:  Remove this once AttachmentPickerWithMenuItems (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript.
                                         displayFileInModal={displayFileInModal}
                                         reportID={reportID}
                                         report={report}
@@ -455,11 +444,11 @@ function ReportActionCompose({
                                         onValueChange={validateCommentMaxLength}
                                     />
                                     <ReportDropUI
-                                        onDrop={(e) => {
+                                        onDrop={(e: DragEvent) => {
                                             if (isAttachmentPreviewActive) {
                                                 return;
                                             }
-                                            const data = lodashGet(e, ['dataTransfer', 'items', 0]);
+                                            const data = e.dataTransfer?.items[0];
                                             displayFileInModal(data);
                                         }}
                                     />
@@ -500,14 +489,13 @@ function ReportActionCompose({
 
 ReportActionCompose.displayName = 'ReportActionCompose';
 
-export default compose(
-    withCurrentUserPersonalDetails,
-    withOnyx({
+export default withCurrentUserPersonalDetails(
+    withOnyx<ReportActionComposeProps, ReportActionComposeOnyxProps>({
         blockedFromConcierge: {
             key: ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE,
         },
         shouldShowComposeInput: {
             key: ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT,
         },
-    }),
-)(ReportActionCompose);
+    })(ReportActionCompose),
+);

From 5e2f31c82fda91671e4c4c1e601a7755335fb67f Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Fri, 19 Jan 2024 11:33:28 +0100
Subject: [PATCH 03/29] fix: resolve comments

---
 .../ReportActionCompose.tsx                   | 23 ++++++++-----------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 2cdb4e4d3602..73b025c50df2 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -88,13 +88,10 @@ type ReportActionComposeProps = {
     /** Height of the list which the composer is part of */
     listHeight?: number;
 
-    /** Whether the composer input should be shown */
-    shouldShowComposeInput?: boolean;
-
     /** The type of action that's pending  */
     pendingAction?: OnyxCommon.PendingAction;
 
-    /** /** Whetjer the report is ready for display */
+    /** Whether the report is ready for display */
     isReportReadyForDisplay?: boolean;
 } & ReportActionComposeOnyxProps &
     WithCurrentUserPersonalDetailsProps;
@@ -212,7 +209,7 @@ function ReportActionCompose({
     }, [report, blockedFromConcierge, translate, conciergePlaceholderRandomIndex]);
 
     const focus = () => {
-        if (composerRef?.current === null) {
+        if (composerRef.current === null) {
             return;
         }
         composerRef.current.focus(true);
@@ -281,9 +278,9 @@ function ReportActionCompose({
      * Add a new comment to this chat
      */
     const submitForm = useCallback(
-        (e?: SyntheticEvent) => {
-            if (e) {
-                e.preventDefault();
+        (event?: SyntheticEvent) => {
+            if (event) {
+                event.preventDefault();
             }
 
             const newComment = composerRef.current?.prepareCommentAndResetComposer();
@@ -301,12 +298,12 @@ function ReportActionCompose({
         isKeyboardVisibleWhenShowingModalRef.current = true;
     }, []);
 
-    const onBlur = useCallback((e: FocusEvent) => {
+    const onBlur = useCallback((event: FocusEvent) => {
         setIsFocused(false);
         if (suggestionsRef.current) {
             suggestionsRef.current.resetSuggestions();
         }
-        if (e.relatedTarget && e.relatedTarget === actionButtonRef.current) {
+        if (event.relatedTarget && event.relatedTarget === actionButtonRef.current) {
             isKeyboardVisibleWhenShowingModalRef.current = true;
         }
     }, []);
@@ -444,11 +441,11 @@ function ReportActionCompose({
                                         onValueChange={validateCommentMaxLength}
                                     />
                                     <ReportDropUI
-                                        onDrop={(e: DragEvent) => {
+                                        onDrop={(event: DragEvent) => {
                                             if (isAttachmentPreviewActive) {
                                                 return;
                                             }
-                                            const data = e.dataTransfer?.items[0];
+                                            const data = event.dataTransfer?.items[0];
                                             displayFileInModal(data);
                                         }}
                                     />
@@ -459,7 +456,7 @@ function ReportActionCompose({
                             <EmojiPickerButton
                                 isDisabled={isBlockedFromConcierge || disabled}
                                 onModalHide={focus}
-                                //  @ts-expect-error TODO: Remove this once EmojiPickerButton is migrated to TypeScript.
+                                //  @ts-expect-error TODO: Remove this once EmojiPickerButton (https://github.com/Expensify/App/issues/25155) is migrated to TypeScript.
                                 onEmojiSelected={(...args) => composerRef.current?.replaceSelectionWithText(...args)}
                                 emojiPickerID={report?.reportID}
                             />

From 2a0e3e21c0b61a5f9409137e29986e4d14c93bac Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Fri, 19 Jan 2024 15:14:18 +0100
Subject: [PATCH 04/29] ref: move SuggestionMention, Suggestions to TS, fixes
 to MentionSuggestions and ReportActionCompose

---
 src/components/MentionSuggestions.tsx         |   6 +-
 .../ReportActionCompose.tsx                   |   9 +-
 ...SuggestionEmoji.js => SuggestionEmoji.tsx} |  84 ++++------
 ...estionMention.js => SuggestionMention.tsx} | 145 ++++++++----------
 .../{Suggestions.js => Suggestions.tsx}       | 119 +++++++-------
 src/types/onyx/OnyxCommon.ts                  |   2 +-
 6 files changed, 159 insertions(+), 206 deletions(-)
 rename src/pages/home/report/ReportActionCompose/{SuggestionEmoji.js => SuggestionEmoji.tsx} (86%)
 rename src/pages/home/report/ReportActionCompose/{SuggestionMention.js => SuggestionMention.tsx} (73%)
 rename src/pages/home/report/ReportActionCompose/{Suggestions.js => Suggestions.tsx} (54%)

diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx
index 459131ecc434..99930f995a3a 100644
--- a/src/components/MentionSuggestions.tsx
+++ b/src/components/MentionSuggestions.tsx
@@ -18,7 +18,7 @@ type Mention = {
     alternateText: string;
 
     /** Email/phone number of the user */
-    login: string;
+    login?: string;
 
     /** Array of icons of the user. We use the first element of this array */
     icons: Icon[];
@@ -32,7 +32,7 @@ type MentionSuggestionsProps = {
     mentions: Mention[];
 
     /** Fired when the user selects a mention */
-    onSelect: () => void;
+    onSelect: (highlightedMentionIndex: number) => void;
 
     /** Mention prefix that follows the @ sign  */
     prefix: string;
@@ -142,3 +142,5 @@ function MentionSuggestions({prefix, mentions, highlightedMentionIndex = 0, onSe
 MentionSuggestions.displayName = 'MentionSuggestions';
 
 export default MentionSuggestions;
+
+export type {Mention};
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 73b025c50df2..025b7142df0d 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -9,6 +9,7 @@ import {runOnJS, setNativeProps, useAnimatedRef} from 'react-native-reanimated';
 import AttachmentModal from '@components/AttachmentModal';
 import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton';
 import ExceededCommentLength from '@components/ExceededCommentLength';
+import type {Mention} from '@components/MentionSuggestions';
 import OfflineIndicator from '@components/OfflineIndicator';
 import OfflineWithFeedback from '@components/OfflineWithFeedback';
 import {usePersonalDetails} from '@components/OnyxProvider';
@@ -51,11 +52,11 @@ type ComposerRef = {
 
 type SuggestionsRef = {
     resetSuggestions: () => void;
-    onSelectionChange: (event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void;
+    onSelectionChange?: (event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void;
     triggerHotkeyActions: (event: KeyboardEvent) => void;
-    updateShouldShowSuggestionMenuToFalse: (shouldShowSuggestionMenu: boolean) => void;
+    updateShouldShowSuggestionMenuToFalse: (shouldShowSuggestionMenu?: boolean) => void;
     setShouldBlockSuggestionCalc: (shouldBlock: boolean) => void;
-    getSuggestions: () => string[];
+    getSuggestions: () => Mention[];
 };
 
 type ReportActionComposeOnyxProps = {
@@ -496,3 +497,5 @@ export default withCurrentUserPersonalDetails(
         },
     })(ReportActionCompose),
 );
+
+export type {SuggestionsRef};
diff --git a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.js b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
similarity index 86%
rename from src/pages/home/report/ReportActionCompose/SuggestionEmoji.js
rename to src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
index e35d1e90bd5a..06089b748554 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.js
+++ b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
@@ -1,5 +1,6 @@
 import PropTypes from 'prop-types';
-import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
+import React, {ForwardedRef, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
+import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
 import _ from 'underscore';
 import EmojiSuggestions from '@components/EmojiSuggestions';
@@ -9,17 +10,15 @@ import * as EmojiUtils from '@libs/EmojiUtils';
 import * as SuggestionsUtils from '@libs/SuggestionUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import * as SuggestionProps from './suggestionProps';
+// eslint-disable-next-line import/no-cycle
+import {SuggestionProps} from './Suggestions';
 
 /**
  * Check if this piece of string looks like an emoji
- * @param {String} str
- * @param {Number} pos
- * @returns {Boolean}
  */
-const isEmojiCode = (str, pos) => {
+const isEmojiCode = (str: string, pos: number): boolean => {
     const leftWords = str.slice(0, pos).split(CONST.REGEX.SPECIAL_CHAR_OR_EMOJI);
-    const leftWord = _.last(leftWords);
+    const leftWord = leftWords.at(-1) ?? '';
     return CONST.REGEX.HAS_COLON_ONLY_AT_THE_BEGINNING.test(leftWord) && leftWord.length > 2;
 };
 
@@ -29,38 +28,33 @@ const defaultSuggestionsValues = {
     shouldShowSuggestionMenu: false,
 };
 
-const propTypes = {
+type SuggestionEmojiOnyxProps = {
     /** Preferred skin tone */
-    preferredSkinTone: PropTypes.number,
-
-    /** A ref to this component */
-    forwardedRef: PropTypes.shape({current: PropTypes.shape({})}),
-
-    /** Function to clear the input */
-    resetKeyboardInput: PropTypes.func.isRequired,
-
-    ...SuggestionProps.baseProps,
+    preferredSkinTone: OnyxEntry<string | number>;
 };
 
-const defaultProps = {
-    preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE,
-    forwardedRef: null,
-};
-
-function SuggestionEmoji({
-    preferredSkinTone,
-    value,
-    setValue,
-    selection,
-    setSelection,
-    updateComment,
-    isComposerFullSize,
-    isAutoSuggestionPickerLarge,
-    forwardedRef,
-    resetKeyboardInput,
-    measureParentContainer,
-    isComposerFocused,
-}) {
+type SuggestionEmojiProps = {
+    /** Function to clear the input */
+    resetKeyboardInput: () => void;
+} & SuggestionEmojiOnyxProps &
+    SuggestionProps;
+
+function SuggestionEmoji(
+    {
+        preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE,
+        value,
+        setValue,
+        selection,
+        setSelection,
+        updateComment,
+        isComposerFullSize,
+        isAutoSuggestionPickerLarge,
+        resetKeyboardInput,
+        measureParentContainer,
+        isComposerFocused,
+    }: SuggestionEmojiProps,
+    ref: ForwardedRef<any>,
+) {
     const [suggestionValues, setSuggestionValues] = useState(defaultSuggestionsValues);
 
     const isEmojiSuggestionsMenuVisible = !_.isEmpty(suggestionValues.suggestedEmojis) && suggestionValues.shouldShowSuggestionMenu;
@@ -214,7 +208,7 @@ function SuggestionEmoji({
     }, []);
 
     useImperativeHandle(
-        forwardedRef,
+        ref,
         () => ({
             resetSuggestions,
             onSelectionChange,
@@ -248,23 +242,11 @@ function SuggestionEmoji({
     );
 }
 
-SuggestionEmoji.propTypes = propTypes;
-SuggestionEmoji.defaultProps = defaultProps;
 SuggestionEmoji.displayName = 'SuggestionEmoji';
 
-const SuggestionEmojiWithRef = React.forwardRef((props, ref) => (
-    <SuggestionEmoji
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        forwardedRef={ref}
-    />
-));
-
-SuggestionEmojiWithRef.displayName = 'SuggestionEmojiWithRef';
-
-export default withOnyx({
+export default withOnyx<SuggestionEmojiProps, SuggestionEmojiOnyxProps>({
     preferredSkinTone: {
         key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
         selector: EmojiUtils.getPreferredSkinToneIndex,
     },
-})(SuggestionEmojiWithRef);
+})(forwardRef(SuggestionEmoji));
diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.js b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
similarity index 73%
rename from src/pages/home/report/ReportActionCompose/SuggestionMention.js
rename to src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
index af3074eec06d..5ce33d916a08 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionMention.js
+++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
@@ -1,7 +1,7 @@
-import PropTypes from 'prop-types';
-import React, {useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
-import _ from 'underscore';
+import type {ForwardedRef} from 'react';
+import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
 import * as Expensicons from '@components/Icon/Expensicons';
+import type {Mention} from '@components/MentionSuggestions';
 import MentionSuggestions from '@components/MentionSuggestions';
 import {usePersonalDetails} from '@components/OnyxProvider';
 import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
@@ -11,52 +11,41 @@ import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
 import * as SuggestionsUtils from '@libs/SuggestionUtils';
 import * as UserUtils from '@libs/UserUtils';
 import CONST from '@src/CONST';
-import * as SuggestionProps from './suggestionProps';
+import type {PersonalDetailsList} from '@src/types/onyx';
+import type {SuggestionsRef} from './ReportActionCompose';
+import type {SuggestionProps} from './Suggestions';
+
+type SuggestionMentionProps = {isAutoSuggestionPickerLarge: boolean} & SuggestionProps;
+
+type SuggestionValues = {
+    suggestedMentions: Mention[];
+    atSignIndex: number;
+    shouldShowSuggestionMenu: boolean;
+    mentionPrefix: string;
+};
 
 /**
  * Check if this piece of string looks like a mention
- * @param {String} str
- * @returns {Boolean}
  */
-const isMentionCode = (str) => CONST.REGEX.HAS_AT_MOST_TWO_AT_SIGNS.test(str);
+const isMentionCode = (str: string): boolean => CONST.REGEX.HAS_AT_MOST_TWO_AT_SIGNS.test(str);
 
-const defaultSuggestionsValues = {
+const defaultSuggestionsValues: SuggestionValues = {
     suggestedMentions: [],
     atSignIndex: -1,
     shouldShowSuggestionMenu: false,
     mentionPrefix: '',
 };
 
-const propTypes = {
-    /** A ref to this component */
-    forwardedRef: PropTypes.shape({current: PropTypes.shape({})}),
-
-    ...SuggestionProps.implementationBaseProps,
-};
-
-const defaultProps = {
-    forwardedRef: null,
-};
-
-function SuggestionMention({
-    value,
-    setValue,
-    selection,
-    setSelection,
-    isComposerFullSize,
-    updateComment,
-    composerHeight,
-    forwardedRef,
-    isAutoSuggestionPickerLarge,
-    measureParentContainer,
-    isComposerFocused,
-}) {
-    const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
+function SuggestionMention(
+    {value, selection, setSelection, updateComment, isAutoSuggestionPickerLarge, measureParentContainer, isComposerFocused}: SuggestionMentionProps,
+    ref: ForwardedRef<SuggestionsRef>,
+) {
+    const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT;
     const {translate, formatPhoneNumber} = useLocalize();
     const previousValue = usePrevious(value);
     const [suggestionValues, setSuggestionValues] = useState(defaultSuggestionsValues);
 
-    const isMentionSuggestionsMenuVisible = !_.isEmpty(suggestionValues.suggestedMentions) && suggestionValues.shouldShowSuggestionMenu;
+    const isMentionSuggestionsMenuVisible = !!suggestionValues.suggestedMentions.length && suggestionValues.shouldShowSuggestionMenu;
 
     const [highlightedMentionIndex, setHighlightedMentionIndex] = useArrowKeyFocusManager({
         isActive: isMentionSuggestionsMenuVisible,
@@ -69,10 +58,9 @@ function SuggestionMention({
 
     /**
      * Replace the code of mention and update selection
-     * @param {Number} highlightedMentionIndex
      */
     const insertSelectedMention = useCallback(
-        (highlightedMentionIndexInner) => {
+        (highlightedMentionIndexInner: number) => {
             const commentBeforeAtSign = value.slice(0, suggestionValues.atSignIndex);
             const mentionObject = suggestionValues.suggestedMentions[highlightedMentionIndexInner];
             const mentionCode = mentionObject.text === CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT ? CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT : `@${mentionObject.login}`;
@@ -100,23 +88,21 @@ function SuggestionMention({
 
     /**
      * Listens for keyboard shortcuts and applies the action
-     *
-     * @param {Object} e
      */
     const triggerHotkeyActions = useCallback(
-        (e) => {
+        (event: KeyboardEvent) => {
             const suggestionsExist = suggestionValues.suggestedMentions.length > 0;
 
-            if (((!e.shiftKey && e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) || e.key === CONST.KEYBOARD_SHORTCUTS.TAB.shortcutKey) && suggestionsExist) {
-                e.preventDefault();
+            if (((!event.shiftKey && event.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) || event.key === CONST.KEYBOARD_SHORTCUTS.TAB.shortcutKey) && suggestionsExist) {
+                event.preventDefault();
                 if (suggestionValues.suggestedMentions.length > 0) {
                     insertSelectedMention(highlightedMentionIndex);
                     return true;
                 }
             }
 
-            if (e.key === CONST.KEYBOARD_SHORTCUTS.ESCAPE.shortcutKey) {
-                e.preventDefault();
+            if (event.key === CONST.KEYBOARD_SHORTCUTS.ESCAPE.shortcutKey) {
+                event.preventDefault();
 
                 if (suggestionsExist) {
                     resetSuggestions();
@@ -129,7 +115,7 @@ function SuggestionMention({
     );
 
     const getMentionOptions = useCallback(
-        (personalDetailsParam, searchValue = '') => {
+        (personalDetailsParam: PersonalDetailsList, searchValue = ''): Mention[] => {
             const suggestions = [];
 
             if (CONST.AUTO_COMPLETE_SUGGESTER.HERE_TEXT.includes(searchValue.toLowerCase())) {
@@ -139,15 +125,15 @@ function SuggestionMention({
                     icons: [
                         {
                             source: Expensicons.Megaphone,
-                            type: 'avatar',
+                            type: CONST.ICON_TYPE_AVATAR,
                         },
                     ],
                 });
             }
 
-            const filteredPersonalDetails = _.filter(_.values(personalDetailsParam), (detail) => {
+            const filteredPersonalDetails = Object.values(personalDetailsParam ?? {}).filter((detail) => {
                 // If we don't have user's primary login, that member is not known to the current user and hence we do not allow them to be mentioned
-                if (!detail.login || detail.isOptimisticPersonalDetail) {
+                if (!detail?.login || detail.isOptimisticPersonalDetail) {
                     return false;
                 }
                 const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(detail);
@@ -158,18 +144,29 @@ function SuggestionMention({
                 return true;
             });
 
-            const sortedPersonalDetails = _.sortBy(filteredPersonalDetails, (detail) => detail.displayName || detail.login);
-            _.each(_.first(sortedPersonalDetails, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS - suggestions.length), (detail) => {
+            const sortedPersonalDetails = filteredPersonalDetails.sort((a, b) => {
+                const nameA = a?.displayName ?? a?.login ?? '';
+                const nameB = b?.displayName ?? b?.login ?? '';
+
+                if (nameA < nameB) {
+                    return -1;
+                }
+                if (nameA > nameB) {
+                    return 1;
+                }
+                return 0;
+            });
+            sortedPersonalDetails.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS - suggestions.length).forEach((detail) => {
                 suggestions.push({
                     text: PersonalDetailsUtils.getDisplayNameOrDefault(detail),
-                    alternateText: formatPhoneNumber(detail.login),
-                    login: detail.login,
+                    alternateText: formatPhoneNumber(detail?.login ?? ''),
+                    login: detail?.login,
                     icons: [
                         {
-                            name: detail.login,
-                            source: UserUtils.getAvatar(detail.avatar, detail.accountID),
-                            type: 'avatar',
-                            fallbackIcon: detail.fallbackIcon,
+                            name: detail?.login,
+                            source: UserUtils.getAvatar(detail?.avatar, detail?.accountID),
+                            type: CONST.ICON_TYPE_AVATAR,
+                            fallbackIcon: detail?.fallbackIcon,
                         },
                     ],
                 });
@@ -181,7 +178,7 @@ function SuggestionMention({
     );
 
     const calculateMentionSuggestion = useCallback(
-        (selectionEnd) => {
+        (selectionEnd: number) => {
             if (shouldBlockCalc.current || selectionEnd < 1 || !isComposerFocused) {
                 shouldBlockCalc.current = false;
                 resetSuggestions();
@@ -201,11 +198,11 @@ function SuggestionMention({
 
             const leftString = value.substring(0, suggestionEndIndex);
             const words = leftString.split(CONST.REGEX.SPACE_OR_EMOJI);
-            const lastWord = _.last(words);
+            const lastWord = words.at(-1) ?? '';
             const secondToLastWord = words[words.length - 3];
 
             let atSignIndex;
-            let suggestionWord;
+            let suggestionWord = '';
             let prefix;
 
             // Detect if the last two words contain a mention (two words are needed to detect a mention with a space in it)
@@ -223,7 +220,7 @@ function SuggestionMention({
                 prefix = lastWord.substring(1);
             }
 
-            const nextState = {
+            const nextState: Partial<SuggestionValues> = {
                 suggestedMentions: [],
                 atSignIndex,
                 mentionPrefix: prefix,
@@ -235,7 +232,7 @@ function SuggestionMention({
                 const suggestions = getMentionOptions(personalDetails, prefix);
 
                 nextState.suggestedMentions = suggestions;
-                nextState.shouldShowSuggestionMenu = !_.isEmpty(suggestions);
+                nextState.shouldShowSuggestionMenu = !!suggestions.length;
             }
 
             setSuggestionValues((prevState) => ({
@@ -268,20 +265,16 @@ function SuggestionMention({
     }, []);
 
     const setShouldBlockSuggestionCalc = useCallback(
-        (shouldBlockSuggestionCalc) => {
+        (shouldBlockSuggestionCalc: boolean) => {
             shouldBlockCalc.current = shouldBlockSuggestionCalc;
         },
         [shouldBlockCalc],
     );
 
-    const onClose = useCallback(() => {
-        setSuggestionValues((prevState) => ({...prevState, suggestedMentions: []}));
-    }, []);
-
     const getSuggestions = useCallback(() => suggestionValues.suggestedMentions, [suggestionValues]);
 
     useImperativeHandle(
-        forwardedRef,
+        ref,
         () => ({
             resetSuggestions,
             triggerHotkeyActions,
@@ -298,34 +291,16 @@ function SuggestionMention({
 
     return (
         <MentionSuggestions
-            onClose={onClose}
             highlightedMentionIndex={highlightedMentionIndex}
             mentions={suggestionValues.suggestedMentions}
-            comment={value}
-            updateComment={setValue}
-            colonIndex={suggestionValues.colonIndex}
             prefix={suggestionValues.mentionPrefix}
             onSelect={insertSelectedMention}
-            isComposerFullSize={isComposerFullSize}
             isMentionPickerLarge={isAutoSuggestionPickerLarge}
-            composerHeight={composerHeight}
             measureParentContainer={measureParentContainer}
         />
     );
 }
 
-SuggestionMention.propTypes = propTypes;
-SuggestionMention.defaultProps = defaultProps;
 SuggestionMention.displayName = 'SuggestionMention';
 
-const SuggestionMentionWithRef = React.forwardRef((props, ref) => (
-    <SuggestionMention
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        forwardedRef={ref}
-    />
-));
-
-SuggestionMentionWithRef.displayName = 'SuggestionMentionWithRef';
-
-export default SuggestionMentionWithRef;
+export default forwardRef(SuggestionMention);
diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.js b/src/pages/home/report/ReportActionCompose/Suggestions.tsx
similarity index 54%
rename from src/pages/home/report/ReportActionCompose/Suggestions.js
rename to src/pages/home/report/ReportActionCompose/Suggestions.tsx
index 5dc71fec6419..f997637a8c4c 100644
--- a/src/pages/home/report/ReportActionCompose/Suggestions.js
+++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx
@@ -1,64 +1,67 @@
-import PropTypes from 'prop-types';
-import React, {useCallback, useContext, useEffect, useImperativeHandle, useRef} from 'react';
+import type {ForwardedRef} from 'react';
+import React, {forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useRef} from 'react';
+import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
 import {View} from 'react-native';
 import {DragAndDropContext} from '@components/DragAndDrop/Provider';
 import usePrevious from '@hooks/usePrevious';
+import type {SuggestionsRef} from './ReportActionCompose';
 import SuggestionEmoji from './SuggestionEmoji';
 import SuggestionMention from './SuggestionMention';
-import * as SuggestionProps from './suggestionProps';
 
-const propTypes = {
-    /** A ref to this component */
-    forwardedRef: PropTypes.shape({current: PropTypes.shape({})}),
-
-    /** Function to clear the input */
-    resetKeyboardInput: PropTypes.func.isRequired,
-
-    /** Is auto suggestion picker large */
-    isAutoSuggestionPickerLarge: PropTypes.bool,
-
-    ...SuggestionProps.baseProps,
+type Selection = {
+    start: number;
+    end: number;
 };
 
-const defaultProps = {
-    forwardedRef: null,
-    isAutoSuggestionPickerLarge: true,
+type SuggestionProps = {
+    value: string;
+    setValue: (newValue: string) => void;
+    selection: Selection;
+    setSelection: (newSelection: Selection) => void;
+    updateComment: (newComment: string, shouldDebounceSaveComment?: boolean) => void;
+    measureParentContainer: () => void;
+    isComposerFullSize: boolean;
+    isComposerFocused?: boolean;
+    resetKeyboardInput: () => void;
+    isAutoSuggestionPickerLarge?: boolean;
+    composerHeight?: number;
 };
 
 /**
  * This component contains the individual suggestion components.
  * If you want to add a new suggestion type, add it here.
  *
- * @returns {React.Component}
  */
-function Suggestions({
-    isComposerFullSize,
-    value,
-    setValue,
-    selection,
-    setSelection,
-    updateComment,
-    composerHeight,
-    forwardedRef,
-    resetKeyboardInput,
-    measureParentContainer,
-    isAutoSuggestionPickerLarge,
-    isComposerFocused,
-}) {
-    const suggestionEmojiRef = useRef(null);
-    const suggestionMentionRef = useRef(null);
+function Suggestions(
+    {
+        isComposerFullSize,
+        value,
+        setValue,
+        selection,
+        setSelection,
+        updateComment,
+        composerHeight,
+        resetKeyboardInput,
+        measureParentContainer,
+        isAutoSuggestionPickerLarge = true,
+        isComposerFocused,
+    }: SuggestionProps,
+    ref: ForwardedRef<SuggestionsRef>,
+) {
+    const suggestionEmojiRef = useRef<SuggestionsRef>(null);
+    const suggestionMentionRef = useRef<SuggestionsRef>(null);
     const {isDraggingOver} = useContext(DragAndDropContext);
     const prevIsDraggingOver = usePrevious(isDraggingOver);
 
     const getSuggestions = useCallback(() => {
-        if (suggestionEmojiRef.current && suggestionEmojiRef.current.getSuggestions) {
+        if (suggestionEmojiRef.current?.getSuggestions) {
             const emojiSuggestions = suggestionEmojiRef.current.getSuggestions();
             if (emojiSuggestions.length > 0) {
                 return emojiSuggestions;
             }
         }
 
-        if (suggestionMentionRef.current && suggestionMentionRef.current.getSuggestions) {
+        if (suggestionMentionRef.current?.getSuggestions) {
             const mentionSuggestions = suggestionMentionRef.current.getSuggestions();
             if (mentionSuggestions.length > 0) {
                 return mentionSuggestions;
@@ -72,38 +75,36 @@ function Suggestions({
      * Clean data related to EmojiSuggestions
      */
     const resetSuggestions = useCallback(() => {
-        suggestionEmojiRef.current.resetSuggestions();
-        suggestionMentionRef.current.resetSuggestions();
+        suggestionEmojiRef.current?.resetSuggestions();
+        suggestionMentionRef.current?.resetSuggestions();
     }, []);
 
     /**
      * Listens for keyboard shortcuts and applies the action
-     *
-     * @param {Object} e
      */
-    const triggerHotkeyActions = useCallback((e) => {
-        const emojiHandler = suggestionEmojiRef.current.triggerHotkeyActions(e);
-        const mentionHandler = suggestionMentionRef.current.triggerHotkeyActions(e);
-        return emojiHandler || mentionHandler;
+    const triggerHotkeyActions = useCallback((e: KeyboardEvent) => {
+        const emojiHandler = suggestionEmojiRef.current?.triggerHotkeyActions(e);
+        const mentionHandler = suggestionMentionRef.current?.triggerHotkeyActions(e);
+        return emojiHandler ?? mentionHandler;
     }, []);
 
-    const onSelectionChange = useCallback((e) => {
-        const emojiHandler = suggestionEmojiRef.current.onSelectionChange(e);
+    const onSelectionChange = useCallback((e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
+        const emojiHandler = suggestionEmojiRef.current?.onSelectionChange(e);
         return emojiHandler;
     }, []);
 
     const updateShouldShowSuggestionMenuToFalse = useCallback(() => {
-        suggestionEmojiRef.current.updateShouldShowSuggestionMenuToFalse();
-        suggestionMentionRef.current.updateShouldShowSuggestionMenuToFalse();
+        suggestionEmojiRef.current?.updateShouldShowSuggestionMenuToFalse();
+        suggestionMentionRef.current?.updateShouldShowSuggestionMenuToFalse();
     }, []);
 
-    const setShouldBlockSuggestionCalc = useCallback((shouldBlock) => {
-        suggestionEmojiRef.current.setShouldBlockSuggestionCalc(shouldBlock);
-        suggestionMentionRef.current.setShouldBlockSuggestionCalc(shouldBlock);
+    const setShouldBlockSuggestionCalc = useCallback((shouldBlock: boolean) => {
+        suggestionEmojiRef.current?.setShouldBlockSuggestionCalc(shouldBlock);
+        suggestionMentionRef.current?.setShouldBlockSuggestionCalc(shouldBlock);
     }, []);
 
     useImperativeHandle(
-        forwardedRef,
+        ref,
         () => ({
             resetSuggestions,
             onSelectionChange,
@@ -152,18 +153,8 @@ function Suggestions({
     );
 }
 
-Suggestions.propTypes = propTypes;
-Suggestions.defaultProps = defaultProps;
 Suggestions.displayName = 'Suggestions';
 
-const SuggestionsWithRef = React.forwardRef((props, ref) => (
-    <Suggestions
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        forwardedRef={ref}
-    />
-));
-
-SuggestionsWithRef.displayName = 'SuggestionsWithRef';
+export default forwardRef(Suggestions);
 
-export default SuggestionsWithRef;
+export type {SuggestionProps};
diff --git a/src/types/onyx/OnyxCommon.ts b/src/types/onyx/OnyxCommon.ts
index b26dc167ed44..a4d29967be1e 100644
--- a/src/types/onyx/OnyxCommon.ts
+++ b/src/types/onyx/OnyxCommon.ts
@@ -20,7 +20,7 @@ type Icon = {
     type: AvatarType;
 
     /** Owner of the avatar. If user, displayName. If workspace, policy name */
-    name: string;
+    name?: string;
 
     /** Avatar id */
     id?: number | string;

From 717a5e8d5f3229cf1611c14382adfb0ae5cd1268 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Sun, 21 Jan 2024 14:38:40 +0100
Subject: [PATCH 05/29] fix: started migrating SuggestionEmoji component

---
 .../ReportActionCompose/SuggestionEmoji.tsx   | 44 ++++++++++---------
 1 file changed, 24 insertions(+), 20 deletions(-)

diff --git a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
index 06089b748554..c5eb13176d44 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
+++ b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
@@ -1,8 +1,7 @@
-import PropTypes from 'prop-types';
-import React, {ForwardedRef, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
+import type {ForwardedRef} from 'react';
+import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
 import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
 import EmojiSuggestions from '@components/EmojiSuggestions';
 import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
 import useLocalize from '@hooks/useLocalize';
@@ -10,22 +9,12 @@ import * as EmojiUtils from '@libs/EmojiUtils';
 import * as SuggestionsUtils from '@libs/SuggestionUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-// eslint-disable-next-line import/no-cycle
-import {SuggestionProps} from './Suggestions';
+import type {SuggestionProps} from './Suggestions';
 
-/**
- * Check if this piece of string looks like an emoji
- */
-const isEmojiCode = (str: string, pos: number): boolean => {
-    const leftWords = str.slice(0, pos).split(CONST.REGEX.SPECIAL_CHAR_OR_EMOJI);
-    const leftWord = leftWords.at(-1) ?? '';
-    return CONST.REGEX.HAS_COLON_ONLY_AT_THE_BEGINNING.test(leftWord) && leftWord.length > 2;
-};
-
-const defaultSuggestionsValues = {
-    suggestedEmojis: [],
-    colonSignIndex: -1,
-    shouldShowSuggestionMenu: false,
+type SuggestionsValue = {
+    suggestedEmojis: any[];
+    colonIndex: number;
+    shouldShowSuggestionMenu: boolean;
 };
 
 type SuggestionEmojiOnyxProps = {
@@ -39,6 +28,21 @@ type SuggestionEmojiProps = {
 } & SuggestionEmojiOnyxProps &
     SuggestionProps;
 
+/**
+ * Check if this piece of string looks like an emoji
+ */
+const isEmojiCode = (str: string, pos: number): boolean => {
+    const leftWords = str.slice(0, pos).split(CONST.REGEX.SPECIAL_CHAR_OR_EMOJI);
+    const leftWord = leftWords.at(-1) ?? '';
+    return CONST.REGEX.HAS_COLON_ONLY_AT_THE_BEGINNING.test(leftWord) && leftWord.length > 2;
+};
+
+const defaultSuggestionsValues: SuggestionsValue = {
+    suggestedEmojis: [],
+    colonIndex: -1,
+    shouldShowSuggestionMenu: false,
+};
+
 function SuggestionEmoji(
     {
         preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE,
@@ -61,7 +65,7 @@ function SuggestionEmoji(
 
     const [highlightedEmojiIndex, setHighlightedEmojiIndex] = useArrowKeyFocusManager({
         isActive: isEmojiSuggestionsMenuVisible,
-        maxIndex: SuggestionsUtils.getMaxArrowIndex(suggestionValues.suggestedEmojis.length, isAutoSuggestionPickerLarge),
+        maxIndex: SuggestionsUtils.getMaxArrowIndex(suggestionValues.suggestedEmojis.length, !!isAutoSuggestionPickerLarge),
         shouldExcludeTextAreaNodes: false,
     });
 
@@ -75,7 +79,7 @@ function SuggestionEmoji(
      * @param {Number} selectedEmoji
      */
     const insertSelectedEmoji = useCallback(
-        (highlightedEmojiIndexInner) => {
+        (highlightedEmojiIndexInner: number) => {
             const commentBeforeColon = value.slice(0, suggestionValues.colonIndex);
             const emojiObject = suggestionValues.suggestedEmojis[highlightedEmojiIndexInner];
             const emojiCode = emojiObject.types && emojiObject.types[preferredSkinTone] ? emojiObject.types[preferredSkinTone] : emojiObject.code;

From 7c7132531962d803f64392171d7c3c9d95fc9b6c Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Mon, 22 Jan 2024 13:55:47 +0100
Subject: [PATCH 06/29] fix: migrate SuggestionEmoji

---
 src/libs/EmojiUtils.ts                        |  2 +-
 .../ReportActionCompose.tsx                   |  2 +-
 .../ReportActionCompose/SuggestionEmoji.tsx   | 51 ++++++++-----------
 .../ReportActionCompose/Suggestions.tsx       |  2 +-
 4 files changed, 25 insertions(+), 32 deletions(-)

diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts
index e34fa0b90fc6..7971e6147c19 100644
--- a/src/libs/EmojiUtils.ts
+++ b/src/libs/EmojiUtils.ts
@@ -384,7 +384,7 @@ function replaceAndExtractEmojis(text: string, preferredSkinTone: number = CONST
  * Suggest emojis when typing emojis prefix after colon
  * @param [limit] - matching emojis limit
  */
-function suggestEmojis(text: string, lang: keyof SupportedLanguage, limit = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined {
+function suggestEmojis(text: string, lang: SupportedLanguage, limit: number = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined {
     // emojisTrie is importing the emoji JSON file on the app starting and we want to avoid it
     const emojisTrie = require('./EmojiTrie').default;
 
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 025b7142df0d..9754d45be834 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -56,7 +56,7 @@ type SuggestionsRef = {
     triggerHotkeyActions: (event: KeyboardEvent) => void;
     updateShouldShowSuggestionMenuToFalse: (shouldShowSuggestionMenu?: boolean) => void;
     setShouldBlockSuggestionCalc: (shouldBlock: boolean) => void;
-    getSuggestions: () => Mention[];
+    getSuggestions: () => Mention[] | Emoji[];
 };
 
 type ReportActionComposeOnyxProps = {
diff --git a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
index c5eb13176d44..dac59065371d 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
+++ b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
@@ -1,25 +1,29 @@
 import type {ForwardedRef} from 'react';
 import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
-import type {OnyxEntry} from 'react-native-onyx';
+import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
+import type {Emoji} from '@assets/emojis/types';
 import EmojiSuggestions from '@components/EmojiSuggestions';
 import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
 import useLocalize from '@hooks/useLocalize';
+import type {SupportedLanguage} from '@libs/EmojiTrie';
 import * as EmojiUtils from '@libs/EmojiUtils';
 import * as SuggestionsUtils from '@libs/SuggestionUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import type {SuggestionsRef} from './ReportActionCompose';
 import type {SuggestionProps} from './Suggestions';
 
 type SuggestionsValue = {
-    suggestedEmojis: any[];
+    suggestedEmojis: Emoji[];
     colonIndex: number;
     shouldShowSuggestionMenu: boolean;
 };
 
 type SuggestionEmojiOnyxProps = {
     /** Preferred skin tone */
-    preferredSkinTone: OnyxEntry<string | number>;
+    preferredSkinTone: number;
 };
 
 type SuggestionEmojiProps = {
@@ -47,21 +51,19 @@ function SuggestionEmoji(
     {
         preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE,
         value,
-        setValue,
         selection,
         setSelection,
         updateComment,
-        isComposerFullSize,
         isAutoSuggestionPickerLarge,
         resetKeyboardInput,
         measureParentContainer,
         isComposerFocused,
     }: SuggestionEmojiProps,
-    ref: ForwardedRef<any>,
+    ref: ForwardedRef<SuggestionsRef>,
 ) {
     const [suggestionValues, setSuggestionValues] = useState(defaultSuggestionsValues);
 
-    const isEmojiSuggestionsMenuVisible = !_.isEmpty(suggestionValues.suggestedEmojis) && suggestionValues.shouldShowSuggestionMenu;
+    const isEmojiSuggestionsMenuVisible = suggestionValues.suggestedEmojis.length > 0 && suggestionValues.shouldShowSuggestionMenu;
 
     const [highlightedEmojiIndex, setHighlightedEmojiIndex] = useArrowKeyFocusManager({
         isActive: isEmojiSuggestionsMenuVisible,
@@ -82,7 +84,7 @@ function SuggestionEmoji(
         (highlightedEmojiIndexInner: number) => {
             const commentBeforeColon = value.slice(0, suggestionValues.colonIndex);
             const emojiObject = suggestionValues.suggestedEmojis[highlightedEmojiIndexInner];
-            const emojiCode = emojiObject.types && emojiObject.types[preferredSkinTone] ? emojiObject.types[preferredSkinTone] : emojiObject.code;
+            const emojiCode = emojiObject.types?.[preferredSkinTone] ? emojiObject.types[preferredSkinTone] : emojiObject.code;
             const commentAfterColonWithEmojiNameRemoved = value.slice(selection.end);
 
             updateComment(`${commentBeforeColon}${emojiCode} ${SuggestionsUtils.trimLeadingSpace(commentAfterColonWithEmojiNameRemoved)}`, true);
@@ -119,11 +121,9 @@ function SuggestionEmoji(
 
     /**
      * Listens for keyboard shortcuts and applies the action
-     *
-     * @param {Object} e
      */
     const triggerHotkeyActions = useCallback(
-        (e) => {
+        (e: KeyboardEvent) => {
             const suggestionsExist = suggestionValues.suggestedEmojis.length > 0;
 
             if (((!e.shiftKey && e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) || e.key === CONST.KEYBOARD_SHORTCUTS.TAB.shortcutKey) && suggestionsExist) {
@@ -151,7 +151,7 @@ function SuggestionEmoji(
      * Calculates and cares about the content of an Emoji Suggester
      */
     const calculateEmojiSuggestion = useCallback(
-        (selectionEnd) => {
+        (selectionEnd: number) => {
             if (shouldBlockCalc.current || !value) {
                 shouldBlockCalc.current = false;
                 resetSuggestions();
@@ -161,16 +161,16 @@ function SuggestionEmoji(
             const colonIndex = leftString.lastIndexOf(':');
             const isCurrentlyShowingEmojiSuggestion = isEmojiCode(value, selectionEnd);
 
-            const nextState = {
+            const nextState: SuggestionsValue = {
                 suggestedEmojis: [],
                 colonIndex,
                 shouldShowSuggestionMenu: false,
             };
-            const newSuggestedEmojis = EmojiUtils.suggestEmojis(leftString, preferredLocale);
+            const newSuggestedEmojis = EmojiUtils.suggestEmojis(leftString, preferredLocale as SupportedLanguage);
 
-            if (newSuggestedEmojis.length && isCurrentlyShowingEmojiSuggestion) {
+            if (newSuggestedEmojis?.length && isCurrentlyShowingEmojiSuggestion) {
                 nextState.suggestedEmojis = newSuggestedEmojis;
-                nextState.shouldShowSuggestionMenu = !_.isEmpty(newSuggestedEmojis);
+                nextState.shouldShowSuggestionMenu = !isEmptyObject(newSuggestedEmojis);
             }
 
             setSuggestionValues((prevState) => ({...prevState, ...nextState}));
@@ -187,7 +187,7 @@ function SuggestionEmoji(
     }, [selection, calculateEmojiSuggestion, isComposerFocused]);
 
     const onSelectionChange = useCallback(
-        (e) => {
+        (e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
             /**
              * we pass here e.nativeEvent.selection.end directly to calculateEmojiSuggestion
              * because in other case calculateEmojiSuggestion will have an old calculation value
@@ -199,7 +199,7 @@ function SuggestionEmoji(
     );
 
     const setShouldBlockSuggestionCalc = useCallback(
-        (shouldBlockSuggestionCalc) => {
+        (shouldBlockSuggestionCalc: boolean) => {
             shouldBlockCalc.current = shouldBlockSuggestionCalc;
         },
         [shouldBlockCalc],
@@ -207,10 +207,6 @@ function SuggestionEmoji(
 
     const getSuggestions = useCallback(() => suggestionValues.suggestedEmojis, [suggestionValues]);
 
-    const resetEmojiSuggestions = useCallback(() => {
-        setSuggestionValues((prevState) => ({...prevState, suggestedEmojis: []}));
-    }, []);
-
     useImperativeHandle(
         ref,
         () => ({
@@ -230,17 +226,12 @@ function SuggestionEmoji(
 
     return (
         <EmojiSuggestions
-            onClose={resetEmojiSuggestions}
             highlightedEmojiIndex={highlightedEmojiIndex}
             emojis={suggestionValues.suggestedEmojis}
-            comment={value}
-            updateComment={setValue}
-            colonIndex={suggestionValues.colonIndex}
             prefix={value.slice(suggestionValues.colonIndex + 1, selection.start)}
             onSelect={insertSelectedEmoji}
-            isComposerFullSize={isComposerFullSize}
             preferredSkinToneIndex={preferredSkinTone}
-            isEmojiPickerLarge={isAutoSuggestionPickerLarge}
+            isEmojiPickerLarge={!!isAutoSuggestionPickerLarge}
             measureParentContainer={measureParentContainer}
         />
     );
@@ -248,9 +239,11 @@ function SuggestionEmoji(
 
 SuggestionEmoji.displayName = 'SuggestionEmoji';
 
+const SuggestionEmojiForwardedRef = forwardRef(SuggestionEmoji);
+
 export default withOnyx<SuggestionEmojiProps, SuggestionEmojiOnyxProps>({
     preferredSkinTone: {
         key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
         selector: EmojiUtils.getPreferredSkinToneIndex,
     },
-})(forwardRef(SuggestionEmoji));
+})(SuggestionEmojiForwardedRef);
diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.tsx b/src/pages/home/report/ReportActionCompose/Suggestions.tsx
index f997637a8c4c..3a009728a915 100644
--- a/src/pages/home/report/ReportActionCompose/Suggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx
@@ -89,7 +89,7 @@ function Suggestions(
     }, []);
 
     const onSelectionChange = useCallback((e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
-        const emojiHandler = suggestionEmojiRef.current?.onSelectionChange(e);
+        const emojiHandler = suggestionEmojiRef.current?.onSelectionChange?.(e);
         return emojiHandler;
     }, []);
 

From be57be30f750da1ca8a1374213907c3c8e8b8eb1 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Mon, 22 Jan 2024 14:11:08 +0100
Subject: [PATCH 07/29] fix: SuggestionEmoji and Suggestions types, migrate
 SendButton

---
 .../{SendButton.js => SendButton.tsx}              | 14 +++++++-------
 .../report/ReportActionCompose/SuggestionEmoji.tsx | 12 +++++-------
 .../report/ReportActionCompose/Suggestions.tsx     |  2 +-
 3 files changed, 13 insertions(+), 15 deletions(-)
 rename src/pages/home/report/ReportActionCompose/{SendButton.js => SendButton.tsx} (90%)

diff --git a/src/pages/home/report/ReportActionCompose/SendButton.js b/src/pages/home/report/ReportActionCompose/SendButton.tsx
similarity index 90%
rename from src/pages/home/report/ReportActionCompose/SendButton.js
rename to src/pages/home/report/ReportActionCompose/SendButton.tsx
index d0b0453ace2f..b6c6200fc7c0 100644
--- a/src/pages/home/report/ReportActionCompose/SendButton.js
+++ b/src/pages/home/report/ReportActionCompose/SendButton.tsx
@@ -1,4 +1,3 @@
-import PropTypes from 'prop-types';
 import React from 'react';
 import {View} from 'react-native';
 import {Gesture, GestureDetector} from 'react-native-gesture-handler';
@@ -11,21 +10,21 @@ import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import CONST from '@src/CONST';
 
-const propTypes = {
+type SendButtonProps = {
     /** Whether the button is disabled */
-    isDisabled: PropTypes.bool.isRequired,
+    isDisabled: boolean;
 
     /** Handle clicking on send button */
-    handleSendMessage: PropTypes.func.isRequired,
+    handleSendMessage: () => void;
 };
 
-function SendButton({isDisabled: isDisabledProp, handleSendMessage}) {
+function SendButton({isDisabled: isDisabledProp, handleSendMessage}: SendButtonProps) {
     const theme = useTheme();
     const styles = useThemeStyles();
     const {translate} = useLocalize();
 
     const Tap = Gesture.Tap()
-        .enabled()
+        .enabled(!isDisabledProp)
         .onEnd(() => {
             handleSendMessage();
         });
@@ -46,6 +45,8 @@ function SendButton({isDisabled: isDisabledProp, handleSendMessage}) {
                         ]}
                         role={CONST.ROLE.BUTTON}
                         accessibilityLabel={translate('common.send')}
+                        accessible
+                        onPress={() => {}}
                     >
                         {({pressed}) => (
                             <Icon
@@ -60,7 +61,6 @@ function SendButton({isDisabled: isDisabledProp, handleSendMessage}) {
     );
 }
 
-SendButton.propTypes = propTypes;
 SendButton.displayName = 'SendButton';
 
 export default SendButton;
diff --git a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
index dac59065371d..9494431660a9 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
+++ b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
@@ -1,4 +1,4 @@
-import type {ForwardedRef} from 'react';
+import type {ForwardedRef, RefAttributes} from 'react';
 import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
 import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
@@ -28,7 +28,7 @@ type SuggestionEmojiOnyxProps = {
 
 type SuggestionEmojiProps = {
     /** Function to clear the input */
-    resetKeyboardInput: () => void;
+    resetKeyboardInput: (() => void) | undefined;
 } & SuggestionEmojiOnyxProps &
     SuggestionProps;
 
@@ -92,7 +92,7 @@ function SuggestionEmoji(
             // In some Android phones keyboard, the text to search for the emoji is not cleared
             // will be added after the user starts typing again on the keyboard. This package is
             // a workaround to reset the keyboard natively.
-            resetKeyboardInput();
+            resetKeyboardInput?.();
 
             setSelection({
                 start: suggestionValues.colonIndex + emojiCode.length + CONST.SPACE_LENGTH,
@@ -239,11 +239,9 @@ function SuggestionEmoji(
 
 SuggestionEmoji.displayName = 'SuggestionEmoji';
 
-const SuggestionEmojiForwardedRef = forwardRef(SuggestionEmoji);
-
-export default withOnyx<SuggestionEmojiProps, SuggestionEmojiOnyxProps>({
+export default withOnyx<SuggestionEmojiProps & RefAttributes<SuggestionsRef>, SuggestionEmojiOnyxProps>({
     preferredSkinTone: {
         key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
         selector: EmojiUtils.getPreferredSkinToneIndex,
     },
-})(SuggestionEmojiForwardedRef);
+})(forwardRef(SuggestionEmoji));
diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.tsx b/src/pages/home/report/ReportActionCompose/Suggestions.tsx
index 3a009728a915..2500af40c7c6 100644
--- a/src/pages/home/report/ReportActionCompose/Suggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx
@@ -22,7 +22,7 @@ type SuggestionProps = {
     measureParentContainer: () => void;
     isComposerFullSize: boolean;
     isComposerFocused?: boolean;
-    resetKeyboardInput: () => void;
+    resetKeyboardInput?: () => void;
     isAutoSuggestionPickerLarge?: boolean;
     composerHeight?: number;
 };

From 929a51a9a8be77696c674b881b7835d85147e481 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Mon, 22 Jan 2024 14:38:55 +0100
Subject: [PATCH 08/29] fix: migrate AttachmentPickerWithMenuItems

---
 src/components/PopoverMenu.tsx                |   2 +-
 ...s.js => AttachmentPickerWithMenuItems.tsx} | 119 ++++++++----------
 2 files changed, 52 insertions(+), 69 deletions(-)
 rename src/pages/home/report/ReportActionCompose/{AttachmentPickerWithMenuItems.js => AttachmentPickerWithMenuItems.tsx} (82%)

diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx
index 17b1a119671a..b411de1103f1 100644
--- a/src/components/PopoverMenu.tsx
+++ b/src/components/PopoverMenu.tsx
@@ -69,7 +69,7 @@ type PopoverMenuProps = Partial<PopoverModalProps> & {
     anchorPosition: AnchorPosition;
 
     /** Ref of the anchor */
-    anchorRef: RefObject<HTMLDivElement>;
+    anchorRef: RefObject<HTMLDivElement | View>;
 
     /** Where the popover should be positioned relative to the anchor points. */
     anchorAlignment?: AnchorAlignment;
diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
similarity index 82%
rename from src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js
rename to src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
index 444dd939142b..547283cc4eb0 100644
--- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.js
+++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
@@ -1,23 +1,23 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
+import {useIsFocused} from '@react-navigation/native';
+import type {FC} from 'react';
 import React, {useCallback, useEffect, useMemo} from 'react';
 import {View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
+import type {SvgProps} from 'react-native-svg';
+import type {ValueOf} from 'type-fest';
 import AttachmentPicker from '@components/AttachmentPicker';
 import Icon from '@components/Icon';
 import * as Expensicons from '@components/Icon/Expensicons';
 import PopoverMenu from '@components/PopoverMenu';
 import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
 import Tooltip from '@components/Tooltip/PopoverAnchorTooltip';
-import withNavigationFocus from '@components/withNavigationFocus';
 import useLocalize from '@hooks/useLocalize';
 import usePrevious from '@hooks/usePrevious';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWindowDimensions from '@hooks/useWindowDimensions';
 import * as Browser from '@libs/Browser';
-import compose from '@libs/compose';
 import Navigation from '@libs/Navigation/Navigation';
 import * as ReportUtils from '@libs/ReportUtils';
 import * as IOU from '@userActions/IOU';
@@ -26,88 +26,77 @@ import * as Task from '@userActions/Task';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
+import type * as OnyxTypes from '@src/types/onyx';
 
-const propTypes = {
-    /** The report currently being looked at */
-    report: PropTypes.shape({
-        /** ID of the report */
-        reportID: PropTypes.string,
+type MoneyRequestOption = {
+    icon: FC<SvgProps>;
+    text: string;
+    onSelected: () => void;
+};
 
-        /** Whether or not the report is in the process of being created */
-        loading: PropTypes.bool,
-    }).isRequired,
+type MoneyRequestOptions = Record<ValueOf<typeof CONST.IOU.TYPE>, MoneyRequestOption>;
 
+type AttachmentPickerWithMenuItemsOnyxProps = {
     /** The policy tied to the report */
-    policy: PropTypes.shape({
-        /** Type of the policy */
-        type: PropTypes.string,
-    }),
+    policy: OnyxEntry<OnyxTypes.Policy>;
+};
 
-    /** The personal details of everyone in the report */
-    reportParticipantIDs: PropTypes.arrayOf(PropTypes.number),
+type AttachmentPickerWithMenuItemsProps = {
+    /** The report currently being looked at */
+    report: OnyxTypes.Report;
 
     /** Callback to open the file in the modal */
-    displayFileInModal: PropTypes.func.isRequired,
+    displayFileInModal: (url: string) => void;
 
     /** Whether or not the full size composer is available */
-    isFullComposerAvailable: PropTypes.bool.isRequired,
+    isFullComposerAvailable: boolean;
 
     /** Whether or not the composer is full size */
-    isComposerFullSize: PropTypes.bool.isRequired,
+    isComposerFullSize: boolean;
 
     /** Whether or not the user is blocked from concierge */
-    isBlockedFromConcierge: PropTypes.bool.isRequired,
+    isBlockedFromConcierge: boolean;
 
     /** Whether or not the attachment picker is disabled */
-    disabled: PropTypes.bool.isRequired,
+    disabled: boolean;
 
     /** Sets the menu visibility */
-    setMenuVisibility: PropTypes.func.isRequired,
+    setMenuVisibility: (isVisible: boolean) => void;
 
     /** Whether or not the menu is visible */
-    isMenuVisible: PropTypes.bool.isRequired,
+    isMenuVisible: boolean;
 
     /** Report ID */
-    reportID: PropTypes.string.isRequired,
+    reportID: string;
 
     /** Called when opening the attachment picker */
-    onTriggerAttachmentPicker: PropTypes.func.isRequired,
+    onTriggerAttachmentPicker: () => void;
 
     /** Called when cancelling the attachment picker */
-    onCanceledAttachmentPicker: PropTypes.func.isRequired,
+    onCanceledAttachmentPicker: () => void;
 
     /** Called when the menu with the items is closed after it was open */
-    onMenuClosed: PropTypes.func.isRequired,
+    onMenuClosed: () => void;
 
     /** Called when the add action button is pressed */
-    onAddActionPressed: PropTypes.func.isRequired,
+    onAddActionPressed: () => void;
 
     /** Called when the menu item is selected */
-    onItemSelected: PropTypes.func.isRequired,
+    onItemSelected: () => void;
 
     /** A ref for the add action button */
-    actionButtonRef: PropTypes.shape({
-        // eslint-disable-next-line react/forbid-prop-types
-        current: PropTypes.object,
-    }).isRequired,
-
-    /** Whether or not the screen is focused */
-    isFocused: PropTypes.bool.isRequired,
+    actionButtonRef: React.RefObject<HTMLDivElement | View>;
 
     /** A function that toggles isScrollLikelyLayoutTriggered flag for a certain period of time */
-    raiseIsScrollLikelyLayoutTriggered: PropTypes.func.isRequired,
-};
+    raiseIsScrollLikelyLayoutTriggered: () => void;
 
-const defaultProps = {
-    reportParticipantIDs: [],
-    policy: {},
-};
+    /** The personal details of everyone in the report */
+    reportParticipantIDs?: number[];
+} & AttachmentPickerWithMenuItemsOnyxProps;
 
 /**
  * This includes the popover of options you see when pressing the + button in the composer.
  * It also contains the attachment picker, as the menu items need to be able to open it.
- *
- * @returns {React.Component}
  */
 function AttachmentPickerWithMenuItems({
     report,
@@ -127,9 +116,9 @@ function AttachmentPickerWithMenuItems({
     onAddActionPressed,
     onItemSelected,
     actionButtonRef,
-    isFocused,
     raiseIsScrollLikelyLayoutTriggered,
-}) {
+}: AttachmentPickerWithMenuItemsProps) {
+    const isFocused = useIsFocused();
     const theme = useTheme();
     const styles = useThemeStyles();
     const {translate} = useLocalize();
@@ -137,10 +126,9 @@ function AttachmentPickerWithMenuItems({
 
     /**
      * Returns the list of IOU Options
-     * @returns {Array<object>}
      */
     const moneyRequestOptions = useMemo(() => {
-        const options = {
+        const options: MoneyRequestOptions = {
             [CONST.IOU.TYPE.SPLIT]: {
                 icon: Expensicons.Receipt,
                 text: translate('iou.splitBill'),
@@ -158,16 +146,15 @@ function AttachmentPickerWithMenuItems({
             },
         };
 
-        return _.map(ReportUtils.getMoneyRequestOptions(report, policy, reportParticipantIDs), (option) => ({
+        return ReportUtils.getMoneyRequestOptions(report, policy, reportParticipantIDs ?? []).map((option) => ({
             ...options[option],
         }));
     }, [report, policy, reportParticipantIDs, translate]);
 
     /**
      * Determines if we can show the task option
-     * @returns {Boolean}
      */
-    const taskOption = useMemo(() => {
+    const taskOption: MoneyRequestOption[] = useMemo(() => {
         if (!ReportUtils.canCreateTaskInReport(report)) {
             return [];
         }
@@ -206,6 +193,7 @@ function AttachmentPickerWithMenuItems({
 
     return (
         <AttachmentPicker>
+            {/* @ts-expect-error TODO: Remove this once SettlementButton (https://github.com/Expensify/App/issues/25134) is migrated to TypeScript. */}
             {({openPicker}) => {
                 const triggerAttachmentPicker = () => {
                     onTriggerAttachmentPicker();
@@ -235,7 +223,7 @@ function AttachmentPickerWithMenuItems({
                                 <Tooltip text={translate('reportActionCompose.collapse')}>
                                     <PressableWithFeedback
                                         onPress={(e) => {
-                                            e.preventDefault();
+                                            e?.preventDefault();
                                             raiseIsScrollLikelyLayoutTriggered();
                                             Report.setIsComposerFullSize(reportID, false);
                                         }}
@@ -257,7 +245,7 @@ function AttachmentPickerWithMenuItems({
                                 <Tooltip text={translate('reportActionCompose.expand')}>
                                     <PressableWithFeedback
                                         onPress={(e) => {
-                                            e.preventDefault();
+                                            e?.preventDefault();
                                             raiseIsScrollLikelyLayoutTriggered();
                                             Report.setIsComposerFullSize(reportID, true);
                                         }}
@@ -279,14 +267,14 @@ function AttachmentPickerWithMenuItems({
                                 <PressableWithFeedback
                                     ref={actionButtonRef}
                                     onPress={(e) => {
-                                        e.preventDefault();
+                                        e?.preventDefault();
                                         if (!isFocused) {
                                             return;
                                         }
                                         onAddActionPressed();
 
                                         // Drop focus to avoid blue focus ring.
-                                        actionButtonRef.current.blur();
+                                        actionButtonRef.current?.blur();
                                         setMenuVisibility(!isMenuVisible);
                                     }}
                                     style={styles.composerSizeButton}
@@ -329,15 +317,10 @@ function AttachmentPickerWithMenuItems({
     );
 }
 
-AttachmentPickerWithMenuItems.propTypes = propTypes;
-AttachmentPickerWithMenuItems.defaultProps = defaultProps;
 AttachmentPickerWithMenuItems.displayName = 'AttachmentPickerWithMenuItems';
 
-export default compose(
-    withNavigationFocus,
-    withOnyx({
-        policy: {
-            key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${lodashGet(report, 'policyID')}`,
-        },
-    }),
-)(AttachmentPickerWithMenuItems);
+export default withOnyx<AttachmentPickerWithMenuItemsProps, AttachmentPickerWithMenuItemsOnyxProps>({
+    policy: {
+        key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`,
+    },
+})(AttachmentPickerWithMenuItems);

From 0cc6502443d17483711b9a26bd010e878e944626 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Mon, 22 Jan 2024 15:09:18 +0100
Subject: [PATCH 09/29] fix: Migrate SilentCommentUpdater

---
 ...estions.js => ComposerWithSuggestions.tsx} |  0
 .../{index.android.js => index.android.tsx}   | 22 ++--------
 .../{index.js => index.tsx}                   | 42 +++----------------
 .../SilentCommentUpdater/types.ts             | 29 +++++++++++++
 4 files changed, 38 insertions(+), 55 deletions(-)
 rename src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/{ComposerWithSuggestions.js => ComposerWithSuggestions.tsx} (100%)
 rename src/pages/home/report/ReportActionCompose/SilentCommentUpdater/{index.android.js => index.android.tsx} (69%)
 rename src/pages/home/report/ReportActionCompose/SilentCommentUpdater/{index.js => index.tsx} (60%)
 create mode 100644 src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts

diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
similarity index 100%
rename from src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js
rename to src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.js b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx
similarity index 69%
rename from src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.js
rename to src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx
index f924a7b59194..cee9cbf794bf 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.js
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx
@@ -1,19 +1,7 @@
-import PropTypes from 'prop-types';
 import {useEffect} from 'react';
 import {withOnyx} from 'react-native-onyx';
 import ONYXKEYS from '@src/ONYXKEYS';
-
-const propTypes = {
-    /** The comment of the report */
-    comment: PropTypes.string,
-
-    /** Updates the comment */
-    updateComment: PropTypes.func.isRequired,
-};
-
-const defaultProps = {
-    comment: '',
-};
+import type {SilentCommentUpdaterOnyxProps, SilentCommentUpdaterProps} from './types';
 
 /**
  * Adding .android component to disable updating comment when prev comment is different
@@ -24,9 +12,8 @@ const defaultProps = {
  * This component doesn't render anything. It runs a side effect to update the comment of a report under certain conditions.
  * It is connected to the actual draft comment in onyx. The comment in onyx might updates multiple times, and we want to avoid
  * re-rendering a UI component for that. That's why the side effect was moved down to a separate component.
- * @returns {null}
  */
-function SilentCommentUpdater({comment, updateComment}) {
+function SilentCommentUpdater({comment = '', updateComment}: SilentCommentUpdaterProps) {
     useEffect(() => {
         updateComment(comment);
         // eslint-disable-next-line react-hooks/exhaustive-deps -- We need to run this on mount
@@ -35,13 +22,10 @@ function SilentCommentUpdater({comment, updateComment}) {
     return null;
 }
 
-SilentCommentUpdater.propTypes = propTypes;
-SilentCommentUpdater.defaultProps = defaultProps;
 SilentCommentUpdater.displayName = 'SilentCommentUpdater';
 
-export default withOnyx({
+export default withOnyx<SilentCommentUpdaterProps, SilentCommentUpdaterOnyxProps>({
     comment: {
         key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`,
-        initialValue: '',
     },
 })(SilentCommentUpdater);
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.js b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
similarity index 60%
rename from src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.js
rename to src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
index 9aa997a892f4..afee06dbc8ff 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.js
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
@@ -1,46 +1,18 @@
-import PropTypes from 'prop-types';
 import {useEffect} from 'react';
 import {withOnyx} from 'react-native-onyx';
 import useLocalize from '@hooks/useLocalize';
 import usePrevious from '@hooks/usePrevious';
 import ONYXKEYS from '@src/ONYXKEYS';
-
-const propTypes = {
-    /** The comment of the report */
-    comment: PropTypes.string,
-
-    /** The report associated with the comment */
-    report: PropTypes.shape({
-        /** The ID of the report */
-        reportID: PropTypes.string,
-    }).isRequired,
-
-    /** The value of the comment */
-    value: PropTypes.string.isRequired,
-
-    /** The ref of the comment */
-    commentRef: PropTypes.shape({
-        /** The current value of the comment */
-        current: PropTypes.string,
-    }).isRequired,
-
-    /** Updates the comment */
-    updateComment: PropTypes.func.isRequired,
-};
-
-const defaultProps = {
-    comment: '',
-};
+import type {SilentCommentUpdaterOnyxProps, SilentCommentUpdaterProps} from './types';
 
 /**
  * This component doesn't render anything. It runs a side effect to update the comment of a report under certain conditions.
  * It is connected to the actual draft comment in onyx. The comment in onyx might updates multiple times, and we want to avoid
  * re-rendering a UI component for that. That's why the side effect was moved down to a separate component.
- * @returns {null}
  */
-function SilentCommentUpdater({comment, commentRef, report, value, updateComment}) {
+function SilentCommentUpdater({comment = '', commentRef, report, value, updateComment}: SilentCommentUpdaterProps) {
     const prevCommentProp = usePrevious(comment);
-    const prevReportId = usePrevious(report.reportID);
+    const prevReportId = usePrevious(report?.reportID);
     const {preferredLocale} = useLocalize();
     const prevPreferredLocale = usePrevious(preferredLocale);
 
@@ -56,21 +28,19 @@ function SilentCommentUpdater({comment, commentRef, report, value, updateComment
 
         // As the report IDs change, make sure to update the composer comment as we need to make sure
         // we do not show incorrect data in there (ie. draft of message from other report).
-        if (preferredLocale === prevPreferredLocale && report.reportID === prevReportId && !shouldSyncComment) {
+        if (preferredLocale === prevPreferredLocale && report?.reportID === prevReportId && !shouldSyncComment) {
             return;
         }
 
         updateComment(comment);
-    }, [prevCommentProp, prevPreferredLocale, prevReportId, comment, preferredLocale, report.reportID, updateComment, value, commentRef]);
+    }, [prevCommentProp, prevPreferredLocale, prevReportId, comment, preferredLocale, report?.reportID, updateComment, value, commentRef]);
 
     return null;
 }
 
-SilentCommentUpdater.propTypes = propTypes;
-SilentCommentUpdater.defaultProps = defaultProps;
 SilentCommentUpdater.displayName = 'SilentCommentUpdater';
 
-export default withOnyx({
+export default withOnyx<SilentCommentUpdaterProps, SilentCommentUpdaterOnyxProps>({
     comment: {
         key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`,
         initialValue: '',
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
new file mode 100644
index 000000000000..60924f24099b
--- /dev/null
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
@@ -0,0 +1,29 @@
+import type {OnyxEntry} from 'react-native-onyx';
+import type {Report} from '@src/types/onyx';
+
+type SilentCommentUpdaterOnyxProps = {
+    /** The comment of the report */
+    comment: OnyxEntry<string>;
+};
+
+type SilentCommentUpdaterProps = {
+    /** Updates the comment */
+    updateComment: (comment: OnyxEntry<string>) => void;
+
+    /** The ID of the report associated with the comment */
+    reportID: string;
+
+    /** The report associated with the comment */
+    report: OnyxEntry<Report>;
+
+    /** The value of the comment */
+    value: string;
+
+    /** The ref of the comment */
+    commentRef: React.RefObject<string>;
+
+    /** The comment of the report */
+    commnet: string;
+} & SilentCommentUpdaterOnyxProps;
+
+export type {SilentCommentUpdaterProps, SilentCommentUpdaterOnyxProps};

From 8ed208cb62610d729bd6a7d1c9da1ec477b96c68 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Tue, 23 Jan 2024 10:49:06 +0100
Subject: [PATCH 10/29] fix: ComposerWithSuggestion migration

---
 .../AttachmentPickerWithMenuItems.tsx         |   8 +-
 .../ComposerWithSuggestions.tsx               | 239 ++++++++++--------
 .../ReportActionCompose.tsx                   |   9 +-
 3 files changed, 146 insertions(+), 110 deletions(-)

diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
index 547283cc4eb0..0dbfbba2759d 100644
--- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
+++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
@@ -43,7 +43,7 @@ type AttachmentPickerWithMenuItemsOnyxProps = {
 
 type AttachmentPickerWithMenuItemsProps = {
     /** The report currently being looked at */
-    report: OnyxTypes.Report;
+    report: OnyxEntry<OnyxTypes.Report>;
 
     /** Callback to open the file in the modal */
     displayFileInModal: (url: string) => void;
@@ -132,17 +132,17 @@ function AttachmentPickerWithMenuItems({
             [CONST.IOU.TYPE.SPLIT]: {
                 icon: Expensicons.Receipt,
                 text: translate('iou.splitBill'),
-                onSelected: () => Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.SPLIT, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report.reportID)),
+                onSelected: () => Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.SPLIT, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report?.reportID ?? '')),
             },
             [CONST.IOU.TYPE.REQUEST]: {
                 icon: Expensicons.MoneyCircle,
                 text: translate('iou.requestMoney'),
-                onSelected: () => Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.REQUEST, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report.reportID)),
+                onSelected: () => Navigation.navigate(ROUTES.MONEY_REQUEST_CREATE.getRoute(CONST.IOU.TYPE.REQUEST, CONST.IOU.OPTIMISTIC_TRANSACTION_ID, report?.reportID ?? '')),
             },
             [CONST.IOU.TYPE.SEND]: {
                 icon: Expensicons.Send,
                 text: translate('iou.sendMoney'),
-                onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND, report.reportID),
+                onSelected: () => IOU.startMoneyRequest(CONST.IOU.TYPE.SEND, report?.reportID),
             },
         };
 
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index 413807b1f992..bce7d80121b4 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -1,11 +1,14 @@
 import {useIsFocused, useNavigation} from '@react-navigation/native';
-import lodashGet from 'lodash/get';
-import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
+import lodashDebounce from 'lodash/debounce';
+import type {ForwardedRef, RefAttributes, RefObject} from 'react';
+import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
+import type {MeasureInWindowOnSuccessCallback} from 'react-native';
 import {findNodeHandle, InteractionManager, NativeModules, View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
+import type {Emoji} from '@assets/emojis/types';
 import Composer from '@components/Composer';
-import withKeyboardState from '@components/withKeyboardState';
+import useKeyboardState from '@hooks/useKeyboardState';
 import useLocalize from '@hooks/useLocalize';
 import usePrevious from '@hooks/usePrevious';
 import useStyleUtils from '@hooks/useStyleUtils';
@@ -14,7 +17,6 @@ import useThemeStyles from '@hooks/useThemeStyles';
 import useWindowDimensions from '@hooks/useWindowDimensions';
 import * as Browser from '@libs/Browser';
 import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
-import compose from '@libs/compose';
 import * as ComposerUtils from '@libs/ComposerUtils';
 import getDraftComment from '@libs/ComposerUtils/getDraftComment';
 import convertToLTRForComposer from '@libs/convertToLTRForComposer';
@@ -28,6 +30,7 @@ import * as ReportUtils from '@libs/ReportUtils';
 import * as SuggestionUtils from '@libs/SuggestionUtils';
 import updateMultilineInputRange from '@libs/updateMultilineInputRange';
 import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside';
+import type {ComposerRef, SuggestionsRef} from '@pages/home/report/ReportActionCompose/ReportActionCompose';
 import SilentCommentUpdater from '@pages/home/report/ReportActionCompose/SilentCommentUpdater';
 import Suggestions from '@pages/home/report/ReportActionCompose/Suggestions';
 import * as EmojiPickerActions from '@userActions/EmojiPickerAction';
@@ -36,17 +39,65 @@ import * as Report from '@userActions/Report';
 import * as User from '@userActions/User';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import type * as OnyxTypes from '@src/types/onyx';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
 import {defaultProps, propTypes} from './composerWithSuggestionsProps';
 
+type ComposerWithSuggestionsOnyxProps = {
+    /** The number of lines the comment should take up */
+    numberOfLines: number;
+
+    /** The parent report actions for the report */
+    parentReportActions: OnyxEntry<OnyxTypes.ReportActions>;
+
+    /** The modal state */
+    modal: OnyxEntry<OnyxTypes.Modal>;
+
+    /** The preferred skin tone of the user */
+    preferredSkinTone: number;
+
+    /** Whether the input is focused */
+    editFocused: boolean;
+};
+type ComposerWithSuggestionsProps = {
+    reportID: string;
+    report: OnyxEntry<OnyxTypes.Report>;
+    reportActions: OnyxTypes.ReportAction[] | undefined;
+    onFocus: () => void;
+    onBlur: (event: FocusEvent) => void;
+    onValueChange: (value: string) => void;
+    isComposerFullSize: boolean;
+    isMenuVisible: boolean;
+    inputPlaceholder: string;
+    displayFileInModal: (fileURL: string) => void;
+    textInputShouldClear: boolean;
+    setTextInputShouldClear: (shouldClear: boolean) => void;
+    isBlockedFromConcierge: boolean;
+    disabled: boolean;
+    isFullComposerAvailable: boolean;
+    setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void;
+    setIsCommentEmpty: (isCommentEmpty: boolean) => void;
+    handleSendMessage: () => void;
+    shouldShowComposeInput: OnyxEntry<boolean>;
+    measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void;
+    listHeight: number;
+    isScrollLikelyLayoutTriggered: RefObject<boolean>;
+    raiseIsScrollLikelyLayoutTriggered: () => void;
+    suggestionsRef: React.RefObject<SuggestionsRef>;
+    animatedRef: RefObject<any>;
+    isNextModalWillOpenRef: RefObject<boolean>;
+    editFocused: boolean;
+} & ComposerWithSuggestionsOnyxProps &
+    Partial<ChildrenProps>;
+
 const {RNTextInputReset} = NativeModules;
 
 const isIOSNative = getPlatform() === CONST.PLATFORM.IOS;
 
 /**
  * Broadcast that the user is typing. Debounced to limit how often we publish client events.
- * @param {String} reportID
  */
-const debouncedBroadcastUserIsTyping = _.debounce((reportID) => {
+const debouncedBroadcastUserIsTyping = lodashDebounce((reportID: string) => {
     Report.broadcastUserIsTyping(reportID);
 }, 100);
 
@@ -61,61 +112,61 @@ const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus();
  * If a component really needs access to these state values it should be put here.
  * However, double check if the component really needs access, as it will re-render
  * on every key press.
- * @param {Object} props
- * @returns {React.Component}
  */
-function ComposerWithSuggestions({
-    // Onyx
-    modal,
-    preferredSkinTone,
-    parentReportActions,
-    numberOfLines,
-    // HOCs
-    isKeyboardShown,
-    // Props: Report
-    reportID,
-    report,
-    reportActions,
-    // Focus
-    onFocus,
-    onBlur,
-    onValueChange,
-    // Composer
-    isComposerFullSize,
-    isMenuVisible,
-    inputPlaceholder,
-    displayFileInModal,
-    textInputShouldClear,
-    setTextInputShouldClear,
-    isBlockedFromConcierge,
-    disabled,
-    isFullComposerAvailable,
-    setIsFullComposerAvailable,
-    setIsCommentEmpty,
-    handleSendMessage,
-    shouldShowComposeInput,
-    measureParentContainer,
-    listHeight,
-    isScrollLikelyLayoutTriggered,
-    raiseIsScrollLikelyLayoutTriggered,
-    // Refs
-    suggestionsRef,
-    animatedRef,
-    forwardedRef,
-    isNextModalWillOpenRef,
-    editFocused,
-    // For testing
-    children,
-}) {
+function ComposerWithSuggestions(
+    {
+        // Onyx
+        modal,
+        preferredSkinTone,
+        parentReportActions,
+        numberOfLines,
+
+        // Props: Report
+        reportID,
+        report,
+        reportActions,
+        // Focus
+        onFocus,
+        onBlur,
+        onValueChange,
+        // Composer
+        isComposerFullSize,
+        isMenuVisible,
+        inputPlaceholder,
+        displayFileInModal,
+        textInputShouldClear,
+        setTextInputShouldClear,
+        isBlockedFromConcierge,
+        disabled,
+        isFullComposerAvailable,
+        setIsFullComposerAvailable,
+        setIsCommentEmpty,
+        handleSendMessage,
+        shouldShowComposeInput,
+        measureParentContainer,
+        listHeight,
+        isScrollLikelyLayoutTriggered,
+        raiseIsScrollLikelyLayoutTriggered,
+        // Refs
+        suggestionsRef,
+        animatedRef,
+        isNextModalWillOpenRef,
+        editFocused,
+        // For testing
+        children,
+    }: ComposerWithSuggestionsProps,
+    ref: ForwardedRef<ComposerRef>,
+) {
+    const {isKeyboardShown} = useKeyboardState();
     const theme = useTheme();
     const styles = useThemeStyles();
     const StyleUtils = useStyleUtils();
     const {preferredLocale} = useLocalize();
     const isFocused = useIsFocused();
     const navigation = useNavigation();
-    const emojisPresentBefore = useRef([]);
+    const emojisPresentBefore = useRef<Emoji[]>([]);
     const [value, setValue] = useState(() => {
-        const draft = getDraftComment(reportID) || '';
+        const draft = getDraftComment(reportID) ?? '';
         if (draft) {
             emojisPresentBefore.current = EmojiUtils.extractEmojis(draft);
         }
@@ -126,9 +177,9 @@ function ComposerWithSuggestions({
     const {isSmallScreenWidth} = useWindowDimensions();
     const maxComposerLines = isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES;
 
-    const isEmptyChat = useMemo(() => _.size(reportActions) === 1, [reportActions]);
-    const parentReportAction = lodashGet(parentReportActions, [report.parentReportActionID]);
-    const shouldAutoFocus = !modal.isVisible && (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction))) && shouldShowComposeInput;
+    const isEmptyChat = useMemo(() => reportActions?.length === 1, [reportActions]);
+    const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? ''];
+    const shouldAutoFocus = !modal?.isVisible && (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction))) && shouldShowComposeInput;
 
     const valueRef = useRef(value);
     valueRef.current = value;
@@ -138,11 +189,11 @@ function ComposerWithSuggestions({
     const [composerHeight, setComposerHeight] = useState(0);
 
     const textInputRef = useRef(null);
-    const insertedEmojisRef = useRef([]);
+    const insertedEmojisRef = useRef<Emoji[]>([]);
 
     const syncSelectionWithOnChangeTextRef = useRef(null);
 
-    const suggestions = lodashGet(suggestionsRef, 'current.getSuggestions', () => [])();
+    const suggestions = suggestionsRef.current?.getSuggestions();
 
     const hasEnoughSpaceForLargeSuggestion = SuggestionUtils.hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, suggestions.length);
 
@@ -159,15 +210,12 @@ function ComposerWithSuggestions({
 
     /**
      * Set the TextInput Ref
-     *
-     * @param {Element} el
-     * @memberof ReportActionCompose
      */
     const setTextInputRef = useCallback(
         (el) => {
             ReportActionComposeFocusManager.composerRef.current = el;
             textInputRef.current = el;
-            if (_.isFunction(animatedRef)) {
+            if (typeof animatedRef === 'function') {
                 animatedRef(el);
             }
         },
@@ -196,7 +244,7 @@ function ComposerWithSuggestions({
      * @param {Boolean} shouldDebounceSaveComment
      */
     const updateComment = useCallback(
-        (commentValue, shouldDebounceSaveComment) => {
+        (commentValue: string, shouldDebounceSaveComment: boolean) => {
             raiseIsScrollLikelyLayoutTriggered();
             const {text: newComment, emojis, cursorPosition} = EmojiUtils.replaceAndExtractEmojis(commentValue, preferredSkinTone, preferredLocale);
             if (!_.isEmpty(emojis)) {
@@ -514,7 +562,7 @@ function ComposerWithSuggestions({
     }, []);
 
     useImperativeHandle(
-        forwardedRef,
+        ref,
         () => ({
             blur,
             focus,
@@ -608,39 +656,28 @@ ComposerWithSuggestions.propTypes = propTypes;
 ComposerWithSuggestions.defaultProps = defaultProps;
 ComposerWithSuggestions.displayName = 'ComposerWithSuggestions';
 
-const ComposerWithSuggestionsWithRef = React.forwardRef((props, ref) => (
-    <ComposerWithSuggestions
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        forwardedRef={ref}
-    />
-));
-
-ComposerWithSuggestionsWithRef.displayName = 'ComposerWithSuggestionsWithRef';
-
-export default compose(
-    withKeyboardState,
-    withOnyx({
-        numberOfLines: {
-            key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES}${reportID}`,
-            // We might not have number of lines in onyx yet, for which the composer would be rendered as null
-            // during the first render, which we want to avoid:
-            initWithStoredValues: false,
-        },
-        modal: {
-            key: ONYXKEYS.MODAL,
-        },
-        preferredSkinTone: {
-            key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
-            selector: EmojiUtils.getPreferredSkinToneIndex,
-        },
-        editFocused: {
-            key: ONYXKEYS.INPUT_FOCUSED,
-        },
-        parentReportActions: {
-            key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`,
-            canEvict: false,
-            initWithStoredValues: false,
-        },
-    }),
-)(ComposerWithSuggestionsWithRef);
+const ComposerWithSuggestionsWithRef = forwardRef(ComposerWithSuggestions);
+
+export default withOnyx<ComposerWithSuggestionsProps & RefAttributes<ComposerRef>, ComposerWithSuggestionsOnyxProps>({
+    numberOfLines: {
+        key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES}${reportID}`,
+        // We might not have number of lines in onyx yet, for which the composer would be rendered as null
+        // during the first render, which we want to avoid:
+        initWithStoredValues: false,
+    },
+    modal: {
+        key: ONYXKEYS.MODAL,
+    },
+    preferredSkinTone: {
+        key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
+        selector: EmojiUtils.getPreferredSkinToneIndex,
+    },
+    editFocused: {
+        key: ONYXKEYS.INPUT_FOCUSED,
+    },
+    parentReportActions: {
+        key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`,
+        canEvict: false,
+        initWithStoredValues: false,
+    },
+})(ComposerWithSuggestionsWithRef);
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 9754d45be834..b46eb145173e 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -6,6 +6,7 @@ import {View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
 import {runOnJS, setNativeProps, useAnimatedRef} from 'react-native-reanimated';
+import type {Emoji} from '@assets/emojis/types';
 import AttachmentModal from '@components/AttachmentModal';
 import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton';
 import ExceededCommentLength from '@components/ExceededCommentLength';
@@ -389,7 +390,6 @@ function ReportActionCompose({
                             {({displayFileInModal}) => (
                                 <>
                                     <AttachmentPickerWithMenuItems
-                                        // @ts-expect-error TODO:  Remove this once AttachmentPickerWithMenuItems (https://github.com/Expensify/App/issues/25130) is migrated to TypeScript.
                                         displayFileInModal={displayFileInModal}
                                         reportID={reportID}
                                         report={report}
@@ -397,7 +397,7 @@ function ReportActionCompose({
                                         isFullComposerAvailable={isFullComposerAvailable}
                                         isComposerFullSize={isComposerFullSize}
                                         isBlockedFromConcierge={isBlockedFromConcierge}
-                                        disabled={disabled}
+                                        disabled={!!disabled}
                                         setMenuVisibility={setMenuVisibility}
                                         isMenuVisible={isMenuVisible}
                                         onTriggerAttachmentPicker={onTriggerAttachmentPicker}
@@ -413,7 +413,6 @@ function ReportActionCompose({
                                     />
                                     <ComposerWithSuggestions
                                         ref={composerRef}
-                                        // @ts-expect-error TODO: Remove this once ComposerWithSuggestions is migrated to TypeScript.
                                         animatedRef={animatedRef}
                                         suggestionsRef={suggestionsRef}
                                         isNextModalWillOpenRef={isNextModalWillOpenRef}
@@ -429,7 +428,7 @@ function ReportActionCompose({
                                         textInputShouldClear={textInputShouldClear}
                                         setTextInputShouldClear={setTextInputShouldClear}
                                         isBlockedFromConcierge={isBlockedFromConcierge}
-                                        disabled={disabled}
+                                        disabled={!!disabled}
                                         isFullComposerAvailable={isFullComposerAvailable}
                                         setIsFullComposerAvailable={setIsFullComposerAvailable}
                                         setIsCommentEmpty={setIsCommentEmpty}
@@ -498,4 +497,4 @@ export default withCurrentUserPersonalDetails(
     })(ReportActionCompose),
 );
 
-export type {SuggestionsRef};
+export type {SuggestionsRef, ComposerRef};

From fef68a4161f23948118c04fe93398d2987b973cf Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Wed, 24 Jan 2024 09:45:39 +0100
Subject: [PATCH 11/29] fix: keep working on ComposerWithSuggestions

---
 src/components/Composer/index.tsx             |   5 +-
 src/libs/updateMultilineInputRange/types.ts   |   2 +-
 .../ComposerWithSuggestions.tsx               | 110 +++++++++---------
 .../{index.e2e.js => index.e2e.tsx}           |   0
 .../{index.js => index.tsx}                   |   0
 .../ReportActionCompose.tsx                   |   2 +-
 .../SilentCommentUpdater/index.tsx            |   2 +-
 .../ReportActionCompose/Suggestions.tsx       |   4 +-
 8 files changed, 65 insertions(+), 60 deletions(-)
 rename src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/{index.e2e.js => index.e2e.tsx} (100%)
 rename src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/{index.js => index.tsx} (100%)

diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx
index 3c2caf020ef7..e78e0c2a4039 100755
--- a/src/components/Composer/index.tsx
+++ b/src/components/Composer/index.tsx
@@ -4,9 +4,8 @@ import type {BaseSyntheticEvent, ForwardedRef} from 'react';
 import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
 import {flushSync} from 'react-dom';
 // eslint-disable-next-line no-restricted-imports
-import type {DimensionValue, NativeSyntheticEvent, Text as RNText, TextInput, TextInputKeyPressEventData, TextInputProps, TextInputSelectionChangeEventData} from 'react-native';
+import type {DimensionValue, NativeSyntheticEvent, Text as RNText, TextInput, TextInputKeyPressEventData, TextInputSelectionChangeEventData} from 'react-native';
 import {StyleSheet, View} from 'react-native';
-import type {AnimatedProps} from 'react-native-reanimated';
 import RNTextInput from '@components/RNTextInput';
 import Text from '@components/Text';
 import useIsScrollBarVisible from '@hooks/useIsScrollBarVisible';
@@ -75,7 +74,7 @@ function Composer(
         shouldContainScroll = false,
         ...props
     }: ComposerProps,
-    ref: ForwardedRef<React.Component<AnimatedProps<TextInputProps>>>,
+    ref: ForwardedRef<TextInput | HTMLInputElement>,
 ) {
     const theme = useTheme();
     const styles = useThemeStyles();
diff --git a/src/libs/updateMultilineInputRange/types.ts b/src/libs/updateMultilineInputRange/types.ts
index d1b134b09a99..ce8f553c51f8 100644
--- a/src/libs/updateMultilineInputRange/types.ts
+++ b/src/libs/updateMultilineInputRange/types.ts
@@ -1,5 +1,5 @@
 import type {TextInput} from 'react-native';
 
-type UpdateMultilineInputRange = (input: HTMLInputElement | TextInput, shouldAutoFocus?: boolean) => void;
+type UpdateMultilineInputRange = (input: HTMLInputElement | TextInput | null, shouldAutoFocus?: boolean) => void;
 
 export default UpdateMultilineInputRange;
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index bce7d80121b4..cb5f2b52ffbc 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -2,10 +2,11 @@ import {useIsFocused, useNavigation} from '@react-navigation/native';
 import lodashDebounce from 'lodash/debounce';
 import type {ForwardedRef, RefAttributes, RefObject} from 'react';
 import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
-import type {MeasureInWindowOnSuccessCallback} from 'react-native';
+import type {LayoutChangeEvent, MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInput, TextInputSelectionChangeEventData} from 'react-native';
 import {findNodeHandle, InteractionManager, NativeModules, View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
+import type {useAnimatedRef} from 'react-native-reanimated';
 import type {Emoji} from '@assets/emojis/types';
 import Composer from '@components/Composer';
 import useKeyboardState from '@hooks/useKeyboardState';
@@ -43,9 +44,16 @@ import type * as OnyxTypes from '@src/types/onyx';
 import type ChildrenProps from '@src/types/utils/ChildrenProps';
 import {defaultProps, propTypes} from './composerWithSuggestionsProps';
 
+type SyncSelection = {
+    position: number;
+    value: string;
+};
+
+type AnimatedRef = ReturnType<typeof useAnimatedRef>;
+
 type ComposerWithSuggestionsOnyxProps = {
     /** The number of lines the comment should take up */
-    numberOfLines: number;
+    numberOfLines: OnyxEntry<number>;
 
     /** The parent report actions for the report */
     parentReportActions: OnyxEntry<OnyxTypes.ReportActions>;
@@ -57,8 +65,9 @@ type ComposerWithSuggestionsOnyxProps = {
     preferredSkinTone: number;
 
     /** Whether the input is focused */
-    editFocused: boolean;
+    editFocused: OnyxEntry<boolean>;
 };
+
 type ComposerWithSuggestionsProps = {
     reportID: string;
     report: OnyxEntry<OnyxTypes.Report>;
@@ -69,7 +78,7 @@ type ComposerWithSuggestionsProps = {
     isComposerFullSize: boolean;
     isMenuVisible: boolean;
     inputPlaceholder: string;
-    displayFileInModal: (fileURL: string) => void;
+    displayFileInModal: (file: File | undefined) => void;
     textInputShouldClear: boolean;
     setTextInputShouldClear: (shouldClear: boolean) => void;
     isBlockedFromConcierge: boolean;
@@ -84,8 +93,8 @@ type ComposerWithSuggestionsProps = {
     isScrollLikelyLayoutTriggered: RefObject<boolean>;
     raiseIsScrollLikelyLayoutTriggered: () => void;
     suggestionsRef: React.RefObject<SuggestionsRef>;
-    animatedRef: RefObject<any>;
-    isNextModalWillOpenRef: RefObject<boolean>;
+    animatedRef: AnimatedRef;
+    isNextModalWillOpenRef: RefObject<boolean | null>;
     editFocused: boolean;
 } & ComposerWithSuggestionsOnyxProps &
     Partial<ChildrenProps>;
@@ -178,7 +187,7 @@ function ComposerWithSuggestions(
     const maxComposerLines = isSmallScreenWidth ? CONST.COMPOSER.MAX_LINES_SMALL_SCREEN : CONST.COMPOSER.MAX_LINES;
 
     const isEmptyChat = useMemo(() => reportActions?.length === 1, [reportActions]);
-    const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? ''];
+    const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? ''] ?? null;
     const shouldAutoFocus = !modal?.isVisible && (shouldFocusInputOnScreenFocus || (isEmptyChat && !ReportActionsUtils.isTransactionThread(parentReportAction))) && shouldShowComposeInput;
 
     const valueRef = useRef(value);
@@ -188,14 +197,14 @@ function ComposerWithSuggestions(
 
     const [composerHeight, setComposerHeight] = useState(0);
 
-    const textInputRef = useRef(null);
+    const textInputRef = useRef<TextInput | null>(null);
     const insertedEmojisRef = useRef<Emoji[]>([]);
 
-    const syncSelectionWithOnChangeTextRef = useRef(null);
+    const syncSelectionWithOnChangeTextRef = useRef<SyncSelection | null>(null);
 
     const suggestions = suggestionsRef.current?.getSuggestions();
 
-    const hasEnoughSpaceForLargeSuggestion = SuggestionUtils.hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, suggestions.length);
+    const hasEnoughSpaceForLargeSuggestion = SuggestionUtils.hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, suggestions?.length ?? 0);
 
     const isAutoSuggestionPickerLarge = !isSmallScreenWidth || (isSmallScreenWidth && hasEnoughSpaceForLargeSuggestion);
 
@@ -212,7 +221,7 @@ function ComposerWithSuggestions(
      * Set the TextInput Ref
      */
     const setTextInputRef = useCallback(
-        (el) => {
+        (el: TextInput) => {
             ReportActionComposeFocusManager.composerRef.current = el;
             textInputRef.current = el;
             if (typeof animatedRef === 'function') {
@@ -231,7 +240,7 @@ function ComposerWithSuggestions(
 
     const debouncedSaveReportComment = useMemo(
         () =>
-            _.debounce((selectedReportID, newComment) => {
+            lodashDebounce((selectedReportID, newComment) => {
                 Report.saveReportComment(selectedReportID, newComment || '');
             }, 1000),
         [],
@@ -239,17 +248,14 @@ function ComposerWithSuggestions(
 
     /**
      * Update the value of the comment in Onyx
-     *
-     * @param {String} comment
-     * @param {Boolean} shouldDebounceSaveComment
      */
     const updateComment = useCallback(
-        (commentValue: string, shouldDebounceSaveComment: boolean) => {
+        (commentValue: string | null, shouldDebounceSaveComment?: boolean) => {
             raiseIsScrollLikelyLayoutTriggered();
-            const {text: newComment, emojis, cursorPosition} = EmojiUtils.replaceAndExtractEmojis(commentValue, preferredSkinTone, preferredLocale);
-            if (!_.isEmpty(emojis)) {
+            const {text: newComment, emojis, cursorPosition} = EmojiUtils.replaceAndExtractEmojis(commentValue ?? '', preferredSkinTone, preferredLocale);
+            if (emojis.length) {
                 const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current);
-                if (!_.isEmpty(newEmojis)) {
+                if (newEmojis.length) {
                     // Ensure emoji suggestions are hidden after inserting emoji even when the selection is not changed
                     if (suggestionsRef.current) {
                         suggestionsRef.current.resetSuggestions();
@@ -269,7 +275,7 @@ function ComposerWithSuggestions(
             emojisPresentBefore.current = emojis;
             setValue(newCommentConverted);
             if (commentValue !== newComment) {
-                const position = Math.max(selection.end + (newComment.length - commentRef.current.length), cursorPosition || 0);
+                const position = Math.max(selection.end + (newComment.length - commentRef.current.length), cursorPosition ?? 0);
 
                 if (isIOSNative) {
                     syncSelectionWithOnChangeTextRef.current = {position, value: newComment};
@@ -316,10 +322,9 @@ function ComposerWithSuggestions(
 
     /**
      * Update the number of lines for a comment in Onyx
-     * @param {Number} numberOfLines
      */
     const updateNumberOfLines = useCallback(
-        (newNumberOfLines) => {
+        (newNumberOfLines: number) => {
             if (newNumberOfLines === numberOfLines) {
                 return;
             }
@@ -328,9 +333,6 @@ function ComposerWithSuggestions(
         [reportID, numberOfLines],
     );
 
-    /**
-     * @returns {String}
-     */
     const prepareCommentAndResetComposer = useCallback(() => {
         const trimmedComment = commentRef.current.trim();
         const commentLength = ReportUtils.getCommentLength(trimmedComment);
@@ -356,11 +358,9 @@ function ComposerWithSuggestions(
 
     /**
      * Callback to add whatever text is chosen into the main input (used f.e as callback for the emoji picker)
-     * @param {String} text
-     * @param {Boolean} shouldAddTrailSpace
      */
     const replaceSelectionWithText = useCallback(
-        (text, shouldAddTrailSpace = true) => {
+        (text: string, shouldAddTrailSpace = true) => {
             const updatedText = shouldAddTrailSpace ? `${text} ` : text;
             const selectionSpaceLength = shouldAddTrailSpace ? CONST.SPACE_LENGTH : 0;
             updateComment(ComposerUtils.insertText(commentRef.current, selection, updatedText));
@@ -373,12 +373,12 @@ function ComposerWithSuggestions(
     );
 
     const triggerHotkeyActions = useCallback(
-        (e) => {
+        (e: KeyboardEvent) => {
             if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) {
                 return;
             }
 
-            if (suggestionsRef.current.triggerHotkeyActions(e)) {
+            if (suggestionsRef.current?.triggerHotkeyActions(e)) {
                 return;
             }
 
@@ -390,14 +390,20 @@ function ComposerWithSuggestions(
 
             // Trigger the edit box for last sent message if ArrowUp is pressed and the comment is empty and Chronos is not in the participants
             const valueLength = valueRef.current.length;
-            if (e.key === CONST.KEYBOARD_SHORTCUTS.ARROW_UP.shortcutKey && textInputRef.current.selectionStart === 0 && valueLength === 0 && !ReportUtils.chatIncludesChronos(report)) {
+            if (
+                e.key === CONST.KEYBOARD_SHORTCUTS.ARROW_UP.shortcutKey &&
+                textInputRef.current &&
+                'selectionStart' in textInputRef.current &&
+                textInputRef.current?.selectionStart === 0 &&
+                valueLength === 0 &&
+                !ReportUtils.chatIncludesChronos(report)
+            ) {
                 e.preventDefault();
-                const lastReportAction = _.find(
-                    [...reportActions, parentReportAction],
+                const lastReportAction = [...(reportActions ?? []), parentReportAction].find(
                     (action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action),
                 );
                 if (lastReportAction) {
-                    Report.saveReportActionDraft(reportID, lastReportAction, _.last(lastReportAction.message).html);
+                    Report.saveReportActionDraft(reportID, lastReportAction, lastReportAction.message?.at(-1)?.html ?? '');
                 }
             }
         },
@@ -405,7 +411,7 @@ function ComposerWithSuggestions(
     );
 
     const onChangeText = useCallback(
-        (commentValue) => {
+        (commentValue: string) => {
             updateComment(commentValue, true);
 
             if (isIOSNative && syncSelectionWithOnChangeTextRef.current) {
@@ -416,7 +422,7 @@ function ComposerWithSuggestions(
                 InteractionManager.runAfterInteractions(() => {
                     // note: this implementation is only available on non-web RN, thus the wrapping
                     // 'if' block contains a redundant (since the ref is only used on iOS) platform check
-                    textInputRef.current.setSelection(positionSnapshot, positionSnapshot);
+                    textInputRef.current?.setSelection(positionSnapshot, positionSnapshot);
                 });
             }
         },
@@ -424,8 +430,8 @@ function ComposerWithSuggestions(
     );
 
     const onSelectionChange = useCallback(
-        (e) => {
-            if (textInputRef.current && textInputRef.current.isFocused() && suggestionsRef.current.onSelectionChange(e)) {
+        (e: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
+            if (textInputRef.current?.isFocused() && suggestionsRef.current?.onSelectionChange?.(e)) {
                 return;
             }
 
@@ -451,7 +457,7 @@ function ComposerWithSuggestions(
 
     /**
      * Focus the composer text input
-     * @param {Boolean} [shouldDelay=false] Impose delay before focusing the composer
+     * @param [shouldDelay=false] Impose delay before focusing the composer
      * @memberof ReportActionCompose
      */
     const focus = useCallback((shouldDelay = false) => {
@@ -475,12 +481,12 @@ function ComposerWithSuggestions(
      */
     const checkComposerVisibility = useCallback(() => {
         // Checking whether the screen is focused or not, helps avoid `modal.isVisible` false when popups are closed, even if the modal is opened.
-        const isComposerCoveredUp = !isFocused || EmojiPickerActions.isEmojiPickerVisible() || isMenuVisible || modal.isVisible || modal.willAlertModalBecomeVisible;
+        const isComposerCoveredUp = !isFocused || EmojiPickerActions.isEmojiPickerVisible() || isMenuVisible || !!modal?.isVisible || modal?.willAlertModalBecomeVisible;
         return !isComposerCoveredUp;
     }, [isMenuVisible, modal, isFocused]);
 
     const focusComposerOnKeyPress = useCallback(
-        (e) => {
+        (e: KeyboardEvent) => {
             const isComposerVisible = checkComposerVisibility();
             if (!isComposerVisible) {
                 return;
@@ -491,7 +497,7 @@ function ComposerWithSuggestions(
             }
 
             // if we're typing on another input/text area, do not focus
-            if (['INPUT', 'TEXTAREA'].includes(e.target.nodeName)) {
+            if (['INPUT', 'TEXTAREA'].includes((e.target as Element)?.nodeName)) {
                 return;
             }
 
@@ -527,17 +533,17 @@ function ComposerWithSuggestions(
         };
     }, [focusComposerOnKeyPress, navigation, setUpComposeFocusManager]);
 
-    const prevIsModalVisible = usePrevious(modal.isVisible);
+    const prevIsModalVisible = usePrevious(modal?.isVisible);
     const prevIsFocused = usePrevious(isFocused);
     useEffect(() => {
-        if (modal.isVisible && !prevIsModalVisible) {
+        if (modal?.isVisible && !prevIsModalVisible) {
             // eslint-disable-next-line no-param-reassign
             isNextModalWillOpenRef.current = false;
         }
         // We want to focus or refocus the input when a modal has been closed or the underlying screen is refocused.
         // We avoid doing this on native platforms since the software keyboard popping
         // open creates a jarring and broken UX.
-        if (!((willBlurTextInputOnTapOutside || shouldAutoFocus) && !isNextModalWillOpenRef.current && !modal.isVisible && isFocused && (prevIsModalVisible || !prevIsFocused))) {
+        if (!((willBlurTextInputOnTapOutside || shouldAutoFocus) && !isNextModalWillOpenRef.current && !modal?.isVisible && isFocused && (!!prevIsModalVisible || !prevIsFocused))) {
             return;
         }
 
@@ -546,11 +552,11 @@ function ComposerWithSuggestions(
             return;
         }
         focus(true);
-    }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal.isVisible, isNextModalWillOpenRef, shouldAutoFocus]);
+    }, [focus, prevIsFocused, editFocused, prevIsModalVisible, isFocused, modal?.isVisible, isNextModalWillOpenRef, shouldAutoFocus]);
 
     useEffect(() => {
         // Scrolls the composer to the bottom and sets the selection to the end, so that longer drafts are easier to edit
-        updateMultilineInputRange(textInputRef.current, shouldAutoFocus);
+        updateMultilineInputRange(textInputRef.current, !!shouldAutoFocus);
 
         if (value.length === 0) {
             return;
@@ -568,7 +574,7 @@ function ComposerWithSuggestions(
             focus,
             replaceSelectionWithText,
             prepareCommentAndResetComposer,
-            isFocused: () => textInputRef.current.isFocused(),
+            isFocused: () => !!textInputRef.current?.isFocused(),
         }),
         [blur, focus, prepareCommentAndResetComposer, replaceSelectionWithText],
     );
@@ -582,7 +588,7 @@ function ComposerWithSuggestions(
             <View style={[StyleUtils.getContainerComposeStyles(), styles.textInputComposeBorder]}>
                 <Composer
                     checkComposerVisibility={checkComposerVisibility}
-                    autoFocus={shouldAutoFocus}
+                    autoFocus={!!shouldAutoFocus}
                     multiline
                     ref={setTextInputRef}
                     placeholder={inputPlaceholder}
@@ -607,10 +613,10 @@ function ComposerWithSuggestions(
                     isComposerFullSize={isComposerFullSize}
                     value={value}
                     testID="composer"
-                    numberOfLines={numberOfLines}
+                    numberOfLines={numberOfLines ?? 0}
                     onNumberOfLinesChange={updateNumberOfLines}
                     shouldCalculateCaretPosition
-                    onLayout={(e) => {
+                    onLayout={(e: LayoutChangeEvent) => {
                         const composerLayoutHeight = e.nativeEvent.layout.height;
                         if (composerHeight === composerLayoutHeight) {
                             return;
@@ -625,7 +631,7 @@ function ComposerWithSuggestions(
             <Suggestions
                 ref={suggestionsRef}
                 isComposerFullSize={isComposerFullSize}
-                isComposerFocused={textInputRef.current && textInputRef.current.isFocused()}
+                isComposerFocused={textInputRef.current?.isFocused()}
                 updateComment={updateComment}
                 composerHeight={composerHeight}
                 measureParentContainer={measureParentContainer}
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
similarity index 100%
rename from src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.js
rename to src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.tsx
similarity index 100%
rename from src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.js
rename to src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.tsx
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index b46eb145173e..d7e02fe29fcd 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -54,7 +54,7 @@ type ComposerRef = {
 type SuggestionsRef = {
     resetSuggestions: () => void;
     onSelectionChange?: (event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => void;
-    triggerHotkeyActions: (event: KeyboardEvent) => void;
+    triggerHotkeyActions: (event: KeyboardEvent) => boolean | undefined;
     updateShouldShowSuggestionMenuToFalse: (shouldShowSuggestionMenu?: boolean) => void;
     setShouldBlockSuggestionCalc: (shouldBlock: boolean) => void;
     getSuggestions: () => Mention[] | Emoji[];
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
index afee06dbc8ff..3f883d46a995 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
@@ -10,7 +10,7 @@ import type {SilentCommentUpdaterOnyxProps, SilentCommentUpdaterProps} from './t
  * It is connected to the actual draft comment in onyx. The comment in onyx might updates multiple times, and we want to avoid
  * re-rendering a UI component for that. That's why the side effect was moved down to a separate component.
  */
-function SilentCommentUpdater({comment = '', commentRef, report, value, updateComment}: SilentCommentUpdaterProps) {
+function SilentCommentUpdater({comment, commentRef, report, value, updateComment}: SilentCommentUpdaterProps) {
     const prevCommentProp = usePrevious(comment);
     const prevReportId = usePrevious(report?.reportID);
     const {preferredLocale} = useLocalize();
diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.tsx b/src/pages/home/report/ReportActionCompose/Suggestions.tsx
index 2500af40c7c6..0968a61c3abb 100644
--- a/src/pages/home/report/ReportActionCompose/Suggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx
@@ -1,6 +1,6 @@
 import type {ForwardedRef} from 'react';
 import React, {forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useRef} from 'react';
-import type {NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
+import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
 import {View} from 'react-native';
 import {DragAndDropContext} from '@components/DragAndDrop/Provider';
 import usePrevious from '@hooks/usePrevious';
@@ -19,7 +19,7 @@ type SuggestionProps = {
     selection: Selection;
     setSelection: (newSelection: Selection) => void;
     updateComment: (newComment: string, shouldDebounceSaveComment?: boolean) => void;
-    measureParentContainer: () => void;
+    measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void;
     isComposerFullSize: boolean;
     isComposerFocused?: boolean;
     resetKeyboardInput?: () => void;

From 223fdd804f52f0adf9eb5891f8cd00da0dd91726 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Wed, 24 Jan 2024 12:51:51 +0100
Subject: [PATCH 12/29] fix: wip

---
 src/components/Composer/index.tsx                      |  2 +-
 src/components/Composer/types.ts                       |  2 +-
 .../ComposerWithSuggestions.tsx                        | 10 ++++++----
 .../ReportActionCompose/SilentCommentUpdater/index.tsx |  7 +++----
 .../ReportActionCompose/SilentCommentUpdater/types.ts  |  3 ---
 5 files changed, 11 insertions(+), 13 deletions(-)

diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx
index e78e0c2a4039..a23a176c5bcd 100755
--- a/src/components/Composer/index.tsx
+++ b/src/components/Composer/index.tsx
@@ -300,7 +300,7 @@ function Composer(
     }, []);
 
     const handleKeyPress = useCallback(
-        (e: NativeSyntheticEvent<TextInputKeyPressEventData>) => {
+        (e: NativeSyntheticEvent<TextInputKeyPressEventData> & KeyboardEvent) => {
             // Prevent onKeyPress from being triggered if the Enter key is pressed while text is being composed
             if (!onKeyPress || isEnterWhileComposition(e as unknown as KeyboardEvent)) {
                 return;
diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts
index d8d88970ea78..6e486771c9aa 100644
--- a/src/components/Composer/types.ts
+++ b/src/components/Composer/types.ts
@@ -74,7 +74,7 @@ type ComposerProps = {
     /** Whether the sull composer is open */
     isComposerFullSize?: boolean;
 
-    onKeyPress?: (event: NativeSyntheticEvent<TextInputKeyPressEventData>) => void;
+    onKeyPress?: (event: KeyboardEvent & NativeSyntheticEvent<TextInputKeyPressEventData>) => void;
 
     onFocus?: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
 
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index cb5f2b52ffbc..034affefd747 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -2,7 +2,7 @@ import {useIsFocused, useNavigation} from '@react-navigation/native';
 import lodashDebounce from 'lodash/debounce';
 import type {ForwardedRef, RefAttributes, RefObject} from 'react';
 import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
-import type {LayoutChangeEvent, MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInput, TextInputSelectionChangeEventData} from 'react-native';
+import type {LayoutChangeEvent, MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputSelectionChangeEventData} from 'react-native';
 import {findNodeHandle, InteractionManager, NativeModules, View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
@@ -73,7 +73,7 @@ type ComposerWithSuggestionsProps = {
     report: OnyxEntry<OnyxTypes.Report>;
     reportActions: OnyxTypes.ReportAction[] | undefined;
     onFocus: () => void;
-    onBlur: (event: FocusEvent) => void;
+    onBlur: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
     onValueChange: (value: string) => void;
     isComposerFullSize: boolean;
     isMenuVisible: boolean;
@@ -222,6 +222,7 @@ function ComposerWithSuggestions(
      */
     const setTextInputRef = useCallback(
         (el: TextInput) => {
+            // @ts-expect-error need to reassign this ref
             ReportActionComposeFocusManager.composerRef.current = el;
             textInputRef.current = el;
             if (typeof animatedRef === 'function') {
@@ -373,7 +374,7 @@ function ComposerWithSuggestions(
     );
 
     const triggerHotkeyActions = useCallback(
-        (e: KeyboardEvent) => {
+        (e: KeyboardEvent & NativeSyntheticEvent<TextInputFocusEventData>) => {
             if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) {
                 return;
             }
@@ -537,6 +538,7 @@ function ComposerWithSuggestions(
     const prevIsFocused = usePrevious(isFocused);
     useEffect(() => {
         if (modal?.isVisible && !prevIsModalVisible) {
+            // @ts-expect-error need to reassign this ref
             // eslint-disable-next-line no-param-reassign
             isNextModalWillOpenRef.current = false;
         }
@@ -682,7 +684,7 @@ export default withOnyx<ComposerWithSuggestionsProps & RefAttributes<ComposerRef
         key: ONYXKEYS.INPUT_FOCUSED,
     },
     parentReportActions: {
-        key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`,
+        key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`,
         canEvict: false,
         initWithStoredValues: false,
     },
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
index 3f883d46a995..626938c235da 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
@@ -10,14 +10,14 @@ import type {SilentCommentUpdaterOnyxProps, SilentCommentUpdaterProps} from './t
  * It is connected to the actual draft comment in onyx. The comment in onyx might updates multiple times, and we want to avoid
  * re-rendering a UI component for that. That's why the side effect was moved down to a separate component.
  */
-function SilentCommentUpdater({comment, commentRef, report, value, updateComment}: SilentCommentUpdaterProps) {
+function SilentCommentUpdater({comment = '', commentRef, report, value, updateComment}: SilentCommentUpdaterProps) {
     const prevCommentProp = usePrevious(comment);
     const prevReportId = usePrevious(report?.reportID);
     const {preferredLocale} = useLocalize();
     const prevPreferredLocale = usePrevious(preferredLocale);
 
     useEffect(() => {
-        updateComment(comment);
+        updateComment(comment ?? null);
         // eslint-disable-next-line react-hooks/exhaustive-deps -- We need to run this on mount
     }, []);
 
@@ -32,7 +32,7 @@ function SilentCommentUpdater({comment, commentRef, report, value, updateComment
             return;
         }
 
-        updateComment(comment);
+        updateComment(comment ?? null);
     }, [prevCommentProp, prevPreferredLocale, prevReportId, comment, preferredLocale, report?.reportID, updateComment, value, commentRef]);
 
     return null;
@@ -43,6 +43,5 @@ SilentCommentUpdater.displayName = 'SilentCommentUpdater';
 export default withOnyx<SilentCommentUpdaterProps, SilentCommentUpdaterOnyxProps>({
     comment: {
         key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`,
-        initialValue: '',
     },
 })(SilentCommentUpdater);
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
index 60924f24099b..591ee43ce6cd 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
@@ -21,9 +21,6 @@ type SilentCommentUpdaterProps = {
 
     /** The ref of the comment */
     commentRef: React.RefObject<string>;
-
-    /** The comment of the report */
-    commnet: string;
 } & SilentCommentUpdaterOnyxProps;
 
 export type {SilentCommentUpdaterProps, SilentCommentUpdaterOnyxProps};

From 05bb0a591c9401ae62f15833afd801237e6af3ee Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Wed, 24 Jan 2024 16:20:44 +0100
Subject: [PATCH 13/29] fix: finished migrating ComposerWithSuggestions,
 removed unused files, start migrating ReportTypeingIndicator

---
 src/components/Composer/index.tsx             |   3 +-
 src/components/Composer/types.ts              |  10 +-
 src/components/MentionSuggestions.tsx         |   3 +-
 src/libs/E2E/client.ts                        |   3 +
 .../ComposerWithSuggestions.tsx               |  37 ++---
 .../composerWithSuggestionsProps.js           | 126 ------------------
 .../ComposerWithSuggestions/index.e2e.tsx     |  18 +--
 .../ReportActionCompose.tsx                   |   9 +-
 .../ReportActionCompose/suggestionProps.js    |  39 ------
 ...Indicator.js => ReportTypingIndicator.tsx} |   0
 src/types/modules/react-native.d.ts           |   5 -
 11 files changed, 46 insertions(+), 207 deletions(-)
 delete mode 100644 src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/composerWithSuggestionsProps.js
 delete mode 100644 src/pages/home/report/ReportActionCompose/suggestionProps.js
 rename src/pages/home/report/{ReportTypingIndicator.js => ReportTypingIndicator.tsx} (100%)

diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx
index a23a176c5bcd..493b37ff3a31 100755
--- a/src/components/Composer/index.tsx
+++ b/src/components/Composer/index.tsx
@@ -300,11 +300,12 @@ function Composer(
     }, []);
 
     const handleKeyPress = useCallback(
-        (e: NativeSyntheticEvent<TextInputKeyPressEventData> & KeyboardEvent) => {
+        (e: NativeSyntheticEvent<TextInputKeyPressEventData>) => {
             // Prevent onKeyPress from being triggered if the Enter key is pressed while text is being composed
             if (!onKeyPress || isEnterWhileComposition(e as unknown as KeyboardEvent)) {
                 return;
             }
+
             onKeyPress(e);
         },
         [onKeyPress],
diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts
index 6e486771c9aa..9565eaf6208f 100644
--- a/src/components/Composer/types.ts
+++ b/src/components/Composer/types.ts
@@ -1,4 +1,4 @@
-import type {NativeSyntheticEvent, StyleProp, TextInputFocusEventData, TextInputKeyPressEventData, TextInputSelectionChangeEventData, TextStyle} from 'react-native';
+import type {NativeSyntheticEvent, StyleProp, TextInputProps, TextInputSelectionChangeEventData, TextStyle} from 'react-native';
 
 type TextSelection = {
     start: number;
@@ -74,14 +74,8 @@ type ComposerProps = {
     /** Whether the sull composer is open */
     isComposerFullSize?: boolean;
 
-    onKeyPress?: (event: KeyboardEvent & NativeSyntheticEvent<TextInputKeyPressEventData>) => void;
-
-    onFocus?: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
-
-    onBlur?: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
-
     /** Should make the input only scroll inside the element avoid scroll out to parent */
     shouldContainScroll?: boolean;
-};
+} & TextInputProps;
 
 export type {TextSelection, ComposerProps};
diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx
index 99930f995a3a..23040a242807 100644
--- a/src/components/MentionSuggestions.tsx
+++ b/src/components/MentionSuggestions.tsx
@@ -1,4 +1,5 @@
 import React, {useCallback} from 'react';
+import type {MeasureInWindowOnSuccessCallback} from 'react-native';
 import {View} from 'react-native';
 import useStyleUtils from '@hooks/useStyleUtils';
 import useTheme from '@hooks/useTheme';
@@ -43,7 +44,7 @@ type MentionSuggestionsProps = {
     isMentionPickerLarge: boolean;
 
     /** Measures the parent container's position and dimensions. */
-    measureParentContainer: () => void;
+    measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void;
 };
 
 /**
diff --git a/src/libs/E2E/client.ts b/src/libs/E2E/client.ts
index 472567cc6c1d..7dc3ad4073b9 100644
--- a/src/libs/E2E/client.ts
+++ b/src/libs/E2E/client.ts
@@ -11,6 +11,9 @@ type TestResult = {
 
 type TestConfig = {
     name: string;
+    reportScreen?: {
+        autoFocus?: boolean;
+    };
 };
 
 type NativeCommandPayload = {
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index 034affefd747..c02bda08cac7 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -2,7 +2,15 @@ import {useIsFocused, useNavigation} from '@react-navigation/native';
 import lodashDebounce from 'lodash/debounce';
 import type {ForwardedRef, RefAttributes, RefObject} from 'react';
 import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
-import type {LayoutChangeEvent, MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputSelectionChangeEventData} from 'react-native';
+import type {
+    LayoutChangeEvent,
+    MeasureInWindowOnSuccessCallback,
+    NativeSyntheticEvent,
+    TextInput,
+    TextInputFocusEventData,
+    TextInputKeyPressEventData,
+    TextInputSelectionChangeEventData,
+} from 'react-native';
 import {findNodeHandle, InteractionManager, NativeModules, View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
@@ -42,7 +50,6 @@ import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type * as OnyxTypes from '@src/types/onyx';
 import type ChildrenProps from '@src/types/utils/ChildrenProps';
-import {defaultProps, propTypes} from './composerWithSuggestionsProps';
 
 type SyncSelection = {
     position: number;
@@ -126,7 +133,7 @@ function ComposerWithSuggestions(
     {
         // Onyx
         modal,
-        preferredSkinTone,
+        preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE,
         parentReportActions,
         numberOfLines,
 
@@ -152,7 +159,7 @@ function ComposerWithSuggestions(
         setIsCommentEmpty,
         handleSendMessage,
         shouldShowComposeInput,
-        measureParentContainer,
+        measureParentContainer = () => {},
         listHeight,
         isScrollLikelyLayoutTriggered,
         raiseIsScrollLikelyLayoutTriggered,
@@ -164,7 +171,7 @@ function ComposerWithSuggestions(
         // For testing
         children,
     }: ComposerWithSuggestionsProps,
-    ref: ForwardedRef<ComposerRef>,
+    ref: ForwardedRef<ComposerRef | null>,
 ) {
     const {isKeyboardShown} = useKeyboardState();
     const theme = useTheme();
@@ -374,32 +381,33 @@ function ComposerWithSuggestions(
     );
 
     const triggerHotkeyActions = useCallback(
-        (e: KeyboardEvent & NativeSyntheticEvent<TextInputFocusEventData>) => {
-            if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) {
+        (event: NativeSyntheticEvent<TextInputKeyPressEventData>) => {
+            const webEvent = event as unknown as KeyboardEvent;
+            if (!webEvent || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) {
                 return;
             }
 
-            if (suggestionsRef.current?.triggerHotkeyActions(e)) {
+            if (suggestionsRef.current?.triggerHotkeyActions(webEvent)) {
                 return;
             }
 
             // Submit the form when Enter is pressed
-            if (e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && !e.shiftKey) {
-                e.preventDefault();
+            if (webEvent.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && !webEvent.shiftKey) {
+                webEvent.preventDefault();
                 handleSendMessage();
             }
 
             // Trigger the edit box for last sent message if ArrowUp is pressed and the comment is empty and Chronos is not in the participants
             const valueLength = valueRef.current.length;
             if (
-                e.key === CONST.KEYBOARD_SHORTCUTS.ARROW_UP.shortcutKey &&
+                webEvent.key === CONST.KEYBOARD_SHORTCUTS.ARROW_UP.shortcutKey &&
                 textInputRef.current &&
                 'selectionStart' in textInputRef.current &&
                 textInputRef.current?.selectionStart === 0 &&
                 valueLength === 0 &&
                 !ReportUtils.chatIncludesChronos(report)
             ) {
-                e.preventDefault();
+                webEvent.preventDefault();
                 const lastReportAction = [...(reportActions ?? []), parentReportAction].find(
                     (action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action),
                 );
@@ -568,7 +576,6 @@ function ComposerWithSuggestions(
 
         // eslint-disable-next-line react-hooks/exhaustive-deps
     }, []);
-
     useImperativeHandle(
         ref,
         () => ({
@@ -660,8 +667,6 @@ function ComposerWithSuggestions(
     );
 }
 
-ComposerWithSuggestions.propTypes = propTypes;
-ComposerWithSuggestions.defaultProps = defaultProps;
 ComposerWithSuggestions.displayName = 'ComposerWithSuggestions';
 
 const ComposerWithSuggestionsWithRef = forwardRef(ComposerWithSuggestions);
@@ -689,3 +694,5 @@ export default withOnyx<ComposerWithSuggestionsProps & RefAttributes<ComposerRef
         initWithStoredValues: false,
     },
 })(ComposerWithSuggestionsWithRef);
+
+export type {ComposerWithSuggestionsProps};
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/composerWithSuggestionsProps.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/composerWithSuggestionsProps.js
deleted file mode 100644
index e2aa1d86af03..000000000000
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/composerWithSuggestionsProps.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import PropTypes from 'prop-types';
-import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
-import CONST from '@src/CONST';
-
-const propTypes = {
-    /** Details about any modals being used */
-    modal: PropTypes.shape({
-        /** Indicates if there is a modal currently visible or not */
-        isVisible: PropTypes.bool,
-    }),
-
-    /** User's preferred skin tone color */
-    preferredSkinTone: PropTypes.number,
-
-    /** Number of lines for the composer */
-    numberOfLines: PropTypes.number,
-
-    /** Whether the keyboard is open or not */
-    isKeyboardShown: PropTypes.bool.isRequired,
-
-    /** The actions from the parent report */
-    parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),
-
-    /** Array of report actions for this report */
-    reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)),
-
-    /** The ID of the report */
-    reportID: PropTypes.string.isRequired,
-
-    /** The report currently being looked at */
-    report: PropTypes.shape({
-        parentReportID: PropTypes.string,
-    }).isRequired,
-
-    /** Callback when the input is focused */
-    onFocus: PropTypes.func.isRequired,
-
-    /** Callback when the input is blurred */
-    onBlur: PropTypes.func.isRequired,
-
-    /** Whether the composer is full size or not */
-    isComposerFullSize: PropTypes.bool.isRequired,
-
-    /** Whether the menu is visible or not */
-    isMenuVisible: PropTypes.bool.isRequired,
-
-    /** Placeholder text for the input */
-    inputPlaceholder: PropTypes.string.isRequired,
-
-    /** Function to display a file in the modal */
-    displayFileInModal: PropTypes.func.isRequired,
-
-    /** Whether the text input should be cleared or not */
-    textInputShouldClear: PropTypes.bool.isRequired,
-
-    /** Function to set whether the text input should be cleared or not */
-    setTextInputShouldClear: PropTypes.func.isRequired,
-
-    /** Whether the user is blocked from concierge or not */
-    isBlockedFromConcierge: PropTypes.bool.isRequired,
-
-    /** Whether the input is disabled or not */
-    disabled: PropTypes.bool.isRequired,
-
-    /** Whether the full composer is available or not */
-    isFullComposerAvailable: PropTypes.bool.isRequired,
-
-    /** Function to set whether the full composer is available or not */
-    setIsFullComposerAvailable: PropTypes.func.isRequired,
-
-    /** Function to set whether the comment is empty or not */
-    setIsCommentEmpty: PropTypes.func.isRequired,
-
-    /** A method to call when the form is submitted */
-    handleSendMessage: PropTypes.func.isRequired,
-
-    /** Whether the compose input is shown or not */
-    shouldShowComposeInput: PropTypes.bool.isRequired,
-
-    /** Meaures the parent container's position and dimensions. */
-    measureParentContainer: PropTypes.func,
-
-    /** Ref for the suggestions component */
-    suggestionsRef: PropTypes.shape({
-        current: PropTypes.shape({
-            /** Update the shouldShowSuggestionMenuToFalse prop */
-            updateShouldShowSuggestionMenuToFalse: PropTypes.func.isRequired,
-
-            /** Trigger hotkey actions */
-            triggerHotkeyActions: PropTypes.func.isRequired,
-
-            /** Check if suggestion calculation should be blocked */
-            setShouldBlockSuggestionCalc: PropTypes.func.isRequired,
-
-            /** Callback when the selection changes */
-            onSelectionChange: PropTypes.func.isRequired,
-        }),
-    }).isRequired,
-
-    /** Ref for the animated view (text input) */
-    animatedRef: PropTypes.func.isRequired,
-
-    /** Ref for the composer */
-    forwardedRef: PropTypes.shape({current: PropTypes.shape({})}),
-
-    /** Ref for the isNextModalWillOpen */
-    isNextModalWillOpenRef: PropTypes.shape({current: PropTypes.bool.isRequired}).isRequired,
-
-    /** A flag to indicate whether the onScroll callback is likely triggered by a layout change (caused by text change) or not */
-    isScrollLikelyLayoutTriggered: PropTypes.shape({current: PropTypes.bool.isRequired}).isRequired,
-
-    /** A function that toggles isScrollLikelyLayoutTriggered flag for a certain period of time */
-    raiseIsScrollLikelyLayoutTriggered: PropTypes.func.isRequired,
-};
-
-const defaultProps = {
-    modal: {},
-    preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE,
-    numberOfLines: undefined,
-    parentReportActions: {},
-    reportActions: [],
-    forwardedRef: null,
-    measureParentContainer: () => {},
-};
-
-export {propTypes, defaultProps};
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
index cbbd1758c9cb..87352981ba9a 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
@@ -1,6 +1,8 @@
-import _ from 'lodash';
-import React, {useEffect} from 'react';
+import type {ForwardedRef, RefObject} from 'react';
+import React, {forwardRef, useEffect} from 'react';
 import E2EClient from '@libs/E2E/client';
+import type {ComposerRef} from '@pages/home/report/ReportActionCompose/ReportActionCompose';
+import type {ComposerWithSuggestionsProps} from './ComposerWithSuggestions';
 import ComposerWithSuggestions from './ComposerWithSuggestions';
 
 let rerenderCount = 0;
@@ -14,20 +16,20 @@ function IncrementRenderCount() {
     return null;
 }
 
-const ComposerWithSuggestionsE2e = React.forwardRef((props, ref) => {
+function ComposerWithSuggestionsE2e(props: ComposerWithSuggestionsProps, ref: ForwardedRef<ComposerRef | null>) {
     // Eventually Auto focus on e2e tests
     useEffect(() => {
-        if (_.get(E2EClient.getCurrentActiveTestConfig(), 'reportScreen.autoFocus', false) === false) {
+        if ((E2EClient.getCurrentActiveTestConfig()?.reportScreen?.autoFocus ?? false) === false) {
             return;
         }
 
         // We need to wait for the component to be mounted before focusing
         setTimeout(() => {
-            if (!ref || !ref.current) {
+            if (!(ref as RefObject<ComposerRef>)?.current) {
                 return;
             }
 
-            ref.current.focus(true);
+            (ref as RefObject<ComposerRef>).current?.focus(true);
         }, 1);
     }, [ref]);
 
@@ -44,9 +46,9 @@ const ComposerWithSuggestionsE2e = React.forwardRef((props, ref) => {
             <IncrementRenderCount />
         </ComposerWithSuggestions>
     );
-});
+}
 
 ComposerWithSuggestionsE2e.displayName = 'ComposerWithSuggestionsE2e';
 
-export default ComposerWithSuggestionsE2e;
+export default forwardRef(ComposerWithSuggestionsE2e);
 export {getRerenderCount, resetRerenderCount};
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index d7e02fe29fcd..70c5f61d7725 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -1,7 +1,7 @@
 import {PortalHost} from '@gorhom/portal';
 import type {SyntheticEvent} from 'react';
 import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInputSelectionChangeEventData} from 'react-native';
+import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInputFocusEventData, TextInputSelectionChangeEventData} from 'react-native';
 import {View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
@@ -123,7 +123,7 @@ function ReportActionCompose({
     const {isMediumScreenWidth, isSmallScreenWidth} = useWindowDimensions();
     const {isOffline} = useNetwork();
     const animatedRef = useAnimatedRef();
-    const actionButtonRef = useRef(null);
+    const actionButtonRef = useRef<View | HTMLDivElement | null>(null);
     const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
     /**
      * Updates the Highlight state of the composer
@@ -300,12 +300,13 @@ function ReportActionCompose({
         isKeyboardVisibleWhenShowingModalRef.current = true;
     }, []);
 
-    const onBlur = useCallback((event: FocusEvent) => {
+    const onBlur = useCallback((event: NativeSyntheticEvent<TextInputFocusEventData>) => {
+        const webEvent = event as unknown as FocusEvent;
         setIsFocused(false);
         if (suggestionsRef.current) {
             suggestionsRef.current.resetSuggestions();
         }
-        if (event.relatedTarget && event.relatedTarget === actionButtonRef.current) {
+        if (webEvent.relatedTarget && webEvent.relatedTarget === actionButtonRef.current) {
             isKeyboardVisibleWhenShowingModalRef.current = true;
         }
     }, []);
diff --git a/src/pages/home/report/ReportActionCompose/suggestionProps.js b/src/pages/home/report/ReportActionCompose/suggestionProps.js
deleted file mode 100644
index 62c29f3d418e..000000000000
--- a/src/pages/home/report/ReportActionCompose/suggestionProps.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import PropTypes from 'prop-types';
-
-const baseProps = {
-    /** The current input value */
-    value: PropTypes.string.isRequired,
-
-    /** Callback to update the current input value */
-    setValue: PropTypes.func.isRequired,
-
-    /** The current selection value */
-    selection: PropTypes.shape({
-        start: PropTypes.number.isRequired,
-        end: PropTypes.number.isRequired,
-    }).isRequired,
-
-    /** Callback to update the current selection */
-    setSelection: PropTypes.func.isRequired,
-
-    /** Whether the composer is expanded */
-    isComposerFullSize: PropTypes.bool.isRequired,
-
-    /** Callback to update the comment draft */
-    updateComment: PropTypes.func.isRequired,
-
-    /** Meaures the parent container's position and dimensions. */
-    measureParentContainer: PropTypes.func.isRequired,
-
-    /** Report composer focus state */
-    isComposerFocused: PropTypes.bool,
-};
-
-const implementationBaseProps = {
-    /** Whether to use the small or the big suggestion picker */
-    isAutoSuggestionPickerLarge: PropTypes.bool.isRequired,
-
-    ...baseProps,
-};
-
-export {baseProps, implementationBaseProps};
diff --git a/src/pages/home/report/ReportTypingIndicator.js b/src/pages/home/report/ReportTypingIndicator.tsx
similarity index 100%
rename from src/pages/home/report/ReportTypingIndicator.js
rename to src/pages/home/report/ReportTypingIndicator.tsx
diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts
index aaa7058737ae..7313a28984fc 100644
--- a/src/types/modules/react-native.d.ts
+++ b/src/types/modules/react-native.d.ts
@@ -279,14 +279,9 @@ declare module 'react-native' {
      * Extracted from react-native-web, packages/react-native-web/src/exports/TextInput/types.js
      */
     interface WebTextInputProps extends WebSharedProps {
-        dir?: 'auto' | 'ltr' | 'rtl';
         disabled?: boolean;
-        enterKeyHint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send';
-        readOnly?: boolean;
     }
     interface TextInputProps extends WebTextInputProps {
-        // TODO: remove once the app is updated to RN 0.73
-        smartInsertDelete?: boolean;
         isFullComposerAvailable?: boolean;
     }
 

From cf84b3b817a85d6baa5c28e2913068e2d487b260 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Wed, 24 Jan 2024 17:47:16 +0100
Subject: [PATCH 14/29] fix: move ReportTypingIndicator to TS

---
 .../home/report/ReportTypingIndicator.tsx     | 27 +++++++++----------
 1 file changed, 13 insertions(+), 14 deletions(-)

diff --git a/src/pages/home/report/ReportTypingIndicator.tsx b/src/pages/home/report/ReportTypingIndicator.tsx
index 785f1e3f6a1e..f7f135e7374e 100755
--- a/src/pages/home/report/ReportTypingIndicator.tsx
+++ b/src/pages/home/report/ReportTypingIndicator.tsx
@@ -1,7 +1,6 @@
-import PropTypes from 'prop-types';
 import React, {useMemo} from 'react';
+import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
 import Text from '@components/Text';
 import TextWithEllipsis from '@components/TextWithEllipsis';
 import useLocalize from '@hooks/useLocalize';
@@ -9,28 +8,30 @@ import useNetwork from '@hooks/useNetwork';
 import useThemeStyles from '@hooks/useThemeStyles';
 import * as ReportUtils from '@libs/ReportUtils';
 import ONYXKEYS from '@src/ONYXKEYS';
+import type {ReportUserIsTyping} from '@src/types/onyx';
 
-const propTypes = {
+type ReportTypingIndicatorOnyxProps = {
     /** Key-value pairs of user accountIDs/logins and whether or not they are typing. Keys are accountIDs or logins. */
-    userTypingStatuses: PropTypes.objectOf(PropTypes.bool),
+    userTypingStatuses: OnyxEntry<ReportUserIsTyping>;
 };
 
-const defaultProps = {
-    userTypingStatuses: {},
-};
+type ReportTypingIndicatorProps = {
+    // eslint-disable-next-line react/no-unused-prop-types
+    reportID: string;
+} & ReportTypingIndicatorOnyxProps;
 
-function ReportTypingIndicator({userTypingStatuses}) {
+function ReportTypingIndicator({userTypingStatuses}: ReportTypingIndicatorProps) {
     const {translate} = useLocalize();
     const {isOffline} = useNetwork();
 
     const styles = useThemeStyles();
-    const usersTyping = useMemo(() => _.filter(_.keys(userTypingStatuses), (loginOrAccountID) => userTypingStatuses[loginOrAccountID]), [userTypingStatuses]);
+    const usersTyping = useMemo(() => Object.keys(userTypingStatuses ?? {}).filter((loginOrAccountID) => userTypingStatuses?.[loginOrAccountID]), [userTypingStatuses]);
     const firstUserTyping = usersTyping[0];
 
     const isUserTypingADisplayName = Number.isNaN(Number(firstUserTyping));
 
     // If we are offline, the user typing statuses are not up-to-date so do not show them
-    if (isOffline || !firstUserTyping) {
+    if (!!isOffline || !firstUserTyping) {
         return null;
     }
 
@@ -40,6 +41,7 @@ function ReportTypingIndicator({userTypingStatuses}) {
     if (usersTyping.length === 1) {
         return (
             <TextWithEllipsis
+                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
                 leadingText={firstUserTypingDisplayName || translate('common.someone')}
                 trailingText={` ${translate('reportTypingIndicator.isTyping')}`}
                 textStyle={[styles.chatItemComposeSecondaryRowSubText]}
@@ -59,13 +61,10 @@ function ReportTypingIndicator({userTypingStatuses}) {
     );
 }
 
-ReportTypingIndicator.propTypes = propTypes;
-ReportTypingIndicator.defaultProps = defaultProps;
 ReportTypingIndicator.displayName = 'ReportTypingIndicator';
 
-export default withOnyx({
+export default withOnyx<ReportTypingIndicatorProps, ReportTypingIndicatorOnyxProps>({
     userTypingStatuses: {
         key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING}${reportID}`,
-        initialValue: {},
     },
 })(ReportTypingIndicator);

From 87f22f7e925b169a76d74e9a3eed6643283306c9 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Wed, 24 Jan 2024 18:26:51 +0100
Subject: [PATCH 15/29] fix: migrate ParticipanLocalTime and ReportDropUI

---
 ...tLocalTime.js => ParticipantLocalTime.tsx} | 35 ++++++++++---------
 .../{ReportDropUI.js => ReportDropUI.tsx}     |  9 ++---
 .../home/report/ReportTypingIndicator.tsx     |  2 +-
 src/types/onyx/IOU.ts                         |  2 ++
 4 files changed, 25 insertions(+), 23 deletions(-)
 rename src/pages/home/report/{ParticipantLocalTime.js => ParticipantLocalTime.tsx} (66%)
 rename src/pages/home/report/{ReportDropUI.js => ReportDropUI.tsx} (87%)

diff --git a/src/pages/home/report/ParticipantLocalTime.js b/src/pages/home/report/ParticipantLocalTime.tsx
similarity index 66%
rename from src/pages/home/report/ParticipantLocalTime.js
rename to src/pages/home/report/ParticipantLocalTime.tsx
index 1992953c959e..c8bbaf031e32 100644
--- a/src/pages/home/report/ParticipantLocalTime.js
+++ b/src/pages/home/report/ParticipantLocalTime.tsx
@@ -1,36 +1,39 @@
-import lodashGet from 'lodash/get';
 import React, {useEffect, useState} from 'react';
 import {View} from 'react-native';
-import participantPropTypes from '@components/participantPropTypes';
 import Text from '@components/Text';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
 import DateUtils from '@libs/DateUtils';
 import Timers from '@libs/Timers';
 import CONST from '@src/CONST';
+import type {PersonalDetails} from '@src/types/onyx';
+import type DeepValueOf from '@src/types/utils/DeepValueOf';
 
-const propTypes = {
+type Locales = DeepValueOf<typeof CONST.LOCALES>;
+
+type ParticipantLocalTimeProps = {
     /** Personal details of the participant */
-    participant: participantPropTypes.isRequired,
+    participant: PersonalDetails;
 
-    ...withLocalizePropTypes,
+    preferredLocale: Locales;
 };
 
-function getParticipantLocalTime(participant, preferredLocale) {
-    const reportRecipientTimezone = lodashGet(participant, 'timezone', CONST.DEFAULT_TIME_ZONE);
-    const reportTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale, null, reportRecipientTimezone.selected);
+function getParticipantLocalTime(participant: PersonalDetails, preferredLocale: Locales) {
+    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+    const reportRecipientTimezone = participant.timezone || CONST.DEFAULT_TIME_ZONE;
+    const reportTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale, undefined, reportRecipientTimezone.selected);
     const currentTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale);
-    const reportRecipientDay = DateUtils.formatToDayOfWeek(reportTimezone);
-    const currentUserDay = DateUtils.formatToDayOfWeek(currentTimezone);
+    const reportRecipientDay = DateUtils.formatToDayOfWeek(reportTimezone.toDateString());
+    const currentUserDay = DateUtils.formatToDayOfWeek(currentTimezone.toDateString());
     if (reportRecipientDay !== currentUserDay) {
         return `${DateUtils.formatToLocalTime(reportTimezone)} ${reportRecipientDay}`;
     }
     return `${DateUtils.formatToLocalTime(reportTimezone)}`;
 }
 
-function ParticipantLocalTime(props) {
+function ParticipantLocalTime({participant, preferredLocale}: ParticipantLocalTimeProps) {
+    const {translate} = useLocalize();
     const styles = useThemeStyles();
-    const {participant, preferredLocale, translate} = props;
 
     const [localTime, setLocalTime] = useState(() => getParticipantLocalTime(participant, preferredLocale));
     useEffect(() => {
@@ -44,7 +47,8 @@ function ParticipantLocalTime(props) {
         };
     }, [participant, preferredLocale]);
 
-    const reportRecipientDisplayName = lodashGet(props, 'participant.firstName') || lodashGet(props, 'participant.displayName');
+    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+    const reportRecipientDisplayName = participant.firstName || participant.displayName;
 
     if (!reportRecipientDisplayName) {
         return null;
@@ -65,7 +69,6 @@ function ParticipantLocalTime(props) {
     );
 }
 
-ParticipantLocalTime.propTypes = propTypes;
 ParticipantLocalTime.displayName = 'ParticipantLocalTime';
 
-export default withLocalize(ParticipantLocalTime);
+export default ParticipantLocalTime;
diff --git a/src/pages/home/report/ReportDropUI.js b/src/pages/home/report/ReportDropUI.tsx
similarity index 87%
rename from src/pages/home/report/ReportDropUI.js
rename to src/pages/home/report/ReportDropUI.tsx
index c1c3b8e506ab..d147d0c0c03d 100644
--- a/src/pages/home/report/ReportDropUI.js
+++ b/src/pages/home/report/ReportDropUI.tsx
@@ -1,4 +1,3 @@
-import PropTypes from 'prop-types';
 import React from 'react';
 import {View} from 'react-native';
 import DragAndDropConsumer from '@components/DragAndDrop/Consumer';
@@ -8,12 +7,11 @@ import Text from '@components/Text';
 import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
 
-const propTypes = {
+type ReportDropUIProps = {
     /** Callback to execute when a file is dropped. */
-    onDrop: PropTypes.func.isRequired,
+    onDrop: () => void;
 };
-
-function ReportDropUI({onDrop}) {
+function ReportDropUI({onDrop}: ReportDropUIProps) {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     return (
@@ -33,6 +31,5 @@ function ReportDropUI({onDrop}) {
 }
 
 ReportDropUI.displayName = 'ReportDropUI';
-ReportDropUI.propTypes = propTypes;
 
 export default ReportDropUI;
diff --git a/src/pages/home/report/ReportTypingIndicator.tsx b/src/pages/home/report/ReportTypingIndicator.tsx
index f7f135e7374e..f62db8bf0337 100755
--- a/src/pages/home/report/ReportTypingIndicator.tsx
+++ b/src/pages/home/report/ReportTypingIndicator.tsx
@@ -16,7 +16,7 @@ type ReportTypingIndicatorOnyxProps = {
 };
 
 type ReportTypingIndicatorProps = {
-    // eslint-disable-next-line react/no-unused-prop-types
+    // eslint-disable-next-line react/no-unused-prop-types -- This is used by withOnyx
     reportID: string;
 } & ReportTypingIndicatorOnyxProps;
 
diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts
index a89b0d4530ef..220af7005c45 100644
--- a/src/types/onyx/IOU.ts
+++ b/src/types/onyx/IOU.ts
@@ -23,3 +23,5 @@ type IOU = {
 };
 
 export default IOU;
+
+export type {Participant};

From 63a63ad6df7a87ceeb99de66e8316200d30721cf Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Thu, 25 Jan 2024 09:36:42 +0100
Subject: [PATCH 16/29] fix: typecheck

---
 src/components/LocaleContextProvider.tsx       |  2 +-
 src/pages/home/report/ParticipantLocalTime.tsx | 12 +++++-------
 src/pages/home/report/ReportDropUI.tsx         |  2 +-
 3 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx
index 7313bb4aa7bb..25b468181b87 100644
--- a/src/components/LocaleContextProvider.tsx
+++ b/src/components/LocaleContextProvider.tsx
@@ -132,4 +132,4 @@ Provider.displayName = 'withOnyx(LocaleContextProvider)';
 
 export {Provider as LocaleContextProvider, LocaleContext};
 
-export type {LocaleContextProps};
+export type {LocaleContextProps, Locale};
diff --git a/src/pages/home/report/ParticipantLocalTime.tsx b/src/pages/home/report/ParticipantLocalTime.tsx
index c8bbaf031e32..5d90659fb96a 100644
--- a/src/pages/home/report/ParticipantLocalTime.tsx
+++ b/src/pages/home/report/ParticipantLocalTime.tsx
@@ -5,24 +5,22 @@ import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
 import DateUtils from '@libs/DateUtils';
 import Timers from '@libs/Timers';
+import type {Locale} from '@src/components/LocaleContextProvider';
 import CONST from '@src/CONST';
 import type {PersonalDetails} from '@src/types/onyx';
-import type DeepValueOf from '@src/types/utils/DeepValueOf';
-
-type Locales = DeepValueOf<typeof CONST.LOCALES>;
 
 type ParticipantLocalTimeProps = {
     /** Personal details of the participant */
     participant: PersonalDetails;
 
-    preferredLocale: Locales;
+    preferredLocale?: Locale;
 };
 
-function getParticipantLocalTime(participant: PersonalDetails, preferredLocale: Locales) {
+function getParticipantLocalTime(participant: PersonalDetails, preferredLocale: Locale | undefined) {
     // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
     const reportRecipientTimezone = participant.timezone || CONST.DEFAULT_TIME_ZONE;
-    const reportTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale, undefined, reportRecipientTimezone.selected);
-    const currentTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale);
+    const reportTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale ?? CONST.LOCALES.DEFAULT, undefined, reportRecipientTimezone.selected);
+    const currentTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale ?? CONST.LOCALES.DEFAULT);
     const reportRecipientDay = DateUtils.formatToDayOfWeek(reportTimezone.toDateString());
     const currentUserDay = DateUtils.formatToDayOfWeek(currentTimezone.toDateString());
     if (reportRecipientDay !== currentUserDay) {
diff --git a/src/pages/home/report/ReportDropUI.tsx b/src/pages/home/report/ReportDropUI.tsx
index d147d0c0c03d..fad58d60bbfa 100644
--- a/src/pages/home/report/ReportDropUI.tsx
+++ b/src/pages/home/report/ReportDropUI.tsx
@@ -9,7 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
 
 type ReportDropUIProps = {
     /** Callback to execute when a file is dropped. */
-    onDrop: () => void;
+    onDrop: (event: DragEvent) => void;
 };
 function ReportDropUI({onDrop}: ReportDropUIProps) {
     const styles = useThemeStyles();

From 1225d237f1123cb89b8e29cdc36a022042e1ad1e Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Thu, 25 Jan 2024 13:47:27 +0100
Subject: [PATCH 17/29] fix: minor fixes

---
 src/pages/home/report/ParticipantLocalTime.tsx              | 5 +++--
 .../ComposerWithSuggestions/ComposerWithSuggestions.tsx     | 2 +-
 src/pages/home/report/ReportActionCompose/SendButton.tsx    | 3 ++-
 .../home/report/ReportActionCompose/SuggestionMention.tsx   | 6 ++++--
 4 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/pages/home/report/ParticipantLocalTime.tsx b/src/pages/home/report/ParticipantLocalTime.tsx
index 5d90659fb96a..e97fad8f20f0 100644
--- a/src/pages/home/report/ParticipantLocalTime.tsx
+++ b/src/pages/home/report/ParticipantLocalTime.tsx
@@ -13,11 +13,12 @@ type ParticipantLocalTimeProps = {
     /** Personal details of the participant */
     participant: PersonalDetails;
 
+    /** The user's preferred locale e.g. 'en', 'es-ES' */
     preferredLocale?: Locale;
 };
 
 function getParticipantLocalTime(participant: PersonalDetails, preferredLocale: Locale | undefined) {
-    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Disabling this line for safeness as nullish coalescing works only if the value is undefined or null
     const reportRecipientTimezone = participant.timezone || CONST.DEFAULT_TIME_ZONE;
     const reportTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale ?? CONST.LOCALES.DEFAULT, undefined, reportRecipientTimezone.selected);
     const currentTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale ?? CONST.LOCALES.DEFAULT);
@@ -45,7 +46,7 @@ function ParticipantLocalTime({participant, preferredLocale}: ParticipantLocalTi
         };
     }, [participant, preferredLocale]);
 
-    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+    // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Disabling this line for safeness as nullish coalescing works only if the value is undefined or null
     const reportRecipientDisplayName = participant.firstName || participant.displayName;
 
     if (!reportRecipientDisplayName) {
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index 8c09f53d3b27..96d2e1a26f6b 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -209,7 +209,7 @@ function ComposerWithSuggestions(
 
     const syncSelectionWithOnChangeTextRef = useRef<SyncSelection | null>(null);
 
-    const suggestions = suggestionsRef.current?.getSuggestions();
+    const suggestions = suggestionsRef.current?.getSuggestions() ?? (() => []);
 
     const hasEnoughSpaceForLargeSuggestion = SuggestionUtils.hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, suggestions?.length ?? 0);
 
diff --git a/src/pages/home/report/ReportActionCompose/SendButton.tsx b/src/pages/home/report/ReportActionCompose/SendButton.tsx
index b6c6200fc7c0..453ee9310f46 100644
--- a/src/pages/home/report/ReportActionCompose/SendButton.tsx
+++ b/src/pages/home/report/ReportActionCompose/SendButton.tsx
@@ -24,7 +24,8 @@ function SendButton({isDisabled: isDisabledProp, handleSendMessage}: SendButtonP
     const {translate} = useLocalize();
 
     const Tap = Gesture.Tap()
-        .enabled(!isDisabledProp)
+        // @ts-expect-error Enabled require argument but when passing something button is not working
+        .enabled()
         .onEnd(() => {
             handleSendMessage();
         });
diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
index d5a3a8983467..02bcd27093e7 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
+++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
@@ -145,8 +145,10 @@ function SuggestionMention(
             });
 
             const sortedPersonalDetails = filteredPersonalDetails.sort((a, b) => {
-                const nameA = a?.displayName ?? a?.login ?? '';
-                const nameB = b?.displayName ?? b?.login ?? '';
+                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Disabling this line for safeness as nullish coalescing works only if the value is undefined or null
+                const nameA = a?.displayName || a?.login || '';
+                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Disabling this line for safeness as nullish coalescing works only if the value is undefined or null
+                const nameB = b?.displayName || b?.login || '';
 
                 if (nameA < nameB) {
                     return -1;

From 4f907d25135d3bdc12e7a5b7971e1d71c69b558a Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Thu, 25 Jan 2024 13:55:20 +0100
Subject: [PATCH 18/29] fix: removed unused export

---
 src/types/onyx/IOU.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/types/onyx/IOU.ts b/src/types/onyx/IOU.ts
index 220af7005c45..a89b0d4530ef 100644
--- a/src/types/onyx/IOU.ts
+++ b/src/types/onyx/IOU.ts
@@ -23,5 +23,3 @@ type IOU = {
 };
 
 export default IOU;
-
-export type {Participant};

From efa41789b32f559b5618d316be79d8b1398e53e4 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Tue, 30 Jan 2024 11:16:15 +0100
Subject: [PATCH 19/29] fix: typecheck

---
 src/libs/ComposerUtils/index.ts | 4 ++--
 src/libs/EmojiUtils.ts          | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/libs/ComposerUtils/index.ts b/src/libs/ComposerUtils/index.ts
index 94bba5d0d00c..de937c0ba915 100644
--- a/src/libs/ComposerUtils/index.ts
+++ b/src/libs/ComposerUtils/index.ts
@@ -18,8 +18,8 @@ function insertText(text: string, selection: Selection, textToInsert: string): s
  * Insert a white space at given index of text
  * @param text - text that needs whitespace to be appended to
  */
-function insertWhiteSpaceAtIndex(text: string, index: number) {
-    return `${text.slice(0, index)} ${text.slice(index)}`;
+function insertWhiteSpaceAtIndex(text: string | null, index: number) {
+    return `${text?.slice(0, index)} ${text?.slice(index)}`;
 }
 
 /**
diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts
index 7971e6147c19..ecbed9c87eff 100644
--- a/src/libs/EmojiUtils.ts
+++ b/src/libs/EmojiUtils.ts
@@ -370,7 +370,7 @@ function replaceEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEF
 /**
  * Find all emojis in a text and replace them with their code.
  */
-function replaceAndExtractEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji {
+function replaceAndExtractEmojis(text: string | null, preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji {
     const {text: convertedText = '', emojis = [], cursorPosition} = replaceEmojis(text, preferredSkinTone, lang);
 
     return {

From 99a4110c3e4dde03061ee0872b51949b4caec0f8 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Tue, 30 Jan 2024 11:58:11 +0100
Subject: [PATCH 20/29] fix: typecheck

---
 src/libs/E2E/tests/chatOpeningTest.e2e.ts                     | 3 ++-
 src/libs/E2E/tests/reportTypingTest.e2e.ts                    | 3 ++-
 src/libs/E2E/types.ts                                         | 2 +-
 src/libs/EmojiUtils.ts                                        | 4 ++--
 .../ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx | 3 ++-
 5 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/libs/E2E/tests/chatOpeningTest.e2e.ts b/src/libs/E2E/tests/chatOpeningTest.e2e.ts
index ef380f847c3f..2e2ce76b348d 100644
--- a/src/libs/E2E/tests/chatOpeningTest.e2e.ts
+++ b/src/libs/E2E/tests/chatOpeningTest.e2e.ts
@@ -1,3 +1,4 @@
+import type {NativeConfig} from 'react-native-config';
 import E2ELogin from '@libs/E2E/actions/e2eLogin';
 import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded';
 import E2EClient from '@libs/E2E/client';
@@ -12,7 +13,7 @@ const test = (config: TestConfig) => {
     // check for login (if already logged in the action will simply resolve)
     console.debug('[E2E] Logging in for chat opening');
 
-    const reportID = getConfigValueOrThrow('reportID', config);
+    const reportID = getConfigValueOrThrow('reportID', config as NativeConfig);
 
     E2ELogin().then((neededLogin) => {
         if (neededLogin) {
diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.ts b/src/libs/E2E/tests/reportTypingTest.e2e.ts
index 4e0678aeb020..17464f7eb8d6 100644
--- a/src/libs/E2E/tests/reportTypingTest.e2e.ts
+++ b/src/libs/E2E/tests/reportTypingTest.e2e.ts
@@ -1,3 +1,4 @@
+import type {NativeConfig} from 'react-native-config';
 import Config from 'react-native-config';
 import E2ELogin from '@libs/E2E/actions/e2eLogin';
 import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded';
@@ -16,7 +17,7 @@ const test = (config: TestConfig) => {
     // check for login (if already logged in the action will simply resolve)
     console.debug('[E2E] Logging in for typing');
 
-    const reportID = getConfigValueOrThrow('reportID', config);
+    const reportID = getConfigValueOrThrow('reportID', config as NativeConfig);
 
     E2ELogin().then((neededLogin) => {
         if (neededLogin) {
diff --git a/src/libs/E2E/types.ts b/src/libs/E2E/types.ts
index 2d48813fa115..93640fbb4ce8 100644
--- a/src/libs/E2E/types.ts
+++ b/src/libs/E2E/types.ts
@@ -20,7 +20,7 @@ type NetworkCacheMap = Record<
 
 type TestConfig = {
     name: string;
-    [key: string]: string;
+    [key: string]: string | {autoFocus: boolean};
 };
 
 export type {SigninParams, IsE2ETestSession, NetworkCacheMap, NetworkCacheEntry, TestConfig};
diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts
index ecbed9c87eff..12319e342fb8 100644
--- a/src/libs/EmojiUtils.ts
+++ b/src/libs/EmojiUtils.ts
@@ -371,11 +371,11 @@ function replaceEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEF
  * Find all emojis in a text and replace them with their code.
  */
 function replaceAndExtractEmojis(text: string | null, preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji {
-    const {text: convertedText = '', emojis = [], cursorPosition} = replaceEmojis(text, preferredSkinTone, lang);
+    const {text: convertedText = '', emojis = [], cursorPosition} = replaceEmojis(text ?? '', preferredSkinTone, lang);
 
     return {
         text: convertedText,
-        emojis: emojis.concat(extractEmojis(text)),
+        emojis: emojis.concat(extractEmojis(text ?? '')),
         cursorPosition,
     };
 }
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
index 87352981ba9a..94e65a48e46b 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
@@ -19,7 +19,8 @@ function IncrementRenderCount() {
 function ComposerWithSuggestionsE2e(props: ComposerWithSuggestionsProps, ref: ForwardedRef<ComposerRef | null>) {
     // Eventually Auto focus on e2e tests
     useEffect(() => {
-        if ((E2EClient.getCurrentActiveTestConfig()?.reportScreen?.autoFocus ?? false) === false) {
+        const testConfig = E2EClient.getCurrentActiveTestConfig();
+        if (testConfig?.reportScreen && typeof testConfig.reportScreen !== 'string' && (testConfig?.reportScreen.autoFocus ?? false) === false) {
             return;
         }
 

From 45c6530265b83384f1b06b8f58725e266aa7d315 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Fri, 2 Feb 2024 16:57:35 +0100
Subject: [PATCH 21/29] fix: typecheck

---
 src/components/AttachmentModal.tsx                   |  8 ++++----
 .../API/parameters/AddCommentOrAttachementParams.ts  |  4 +++-
 src/libs/ReportUtils.ts                              |  5 +++--
 src/libs/actions/Report.ts                           |  5 +++--
 .../AttachmentPickerWithMenuItems.tsx                |  3 ++-
 .../ComposerWithSuggestions.tsx                      |  7 +++++--
 .../ReportActionCompose/ReportActionCompose.tsx      | 12 +++++-------
 .../SilentCommentUpdater/types.ts                    |  4 ----
 src/types/onyx/ReportAction.ts                       |  3 ++-
 9 files changed, 27 insertions(+), 24 deletions(-)

diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx
index 90954c63b751..f4b016b790de 100755
--- a/src/components/AttachmentModal.tsx
+++ b/src/components/AttachmentModal.tsx
@@ -80,7 +80,7 @@ type ImagePickerResponse = {
 type FileObject = File | ImagePickerResponse;
 
 type ChildrenProps = {
-    displayFileInModal: (data: FileObject) => void;
+    displayFileInModal: (data: FileObject | undefined) => void;
     show: () => void;
 };
 
@@ -317,8 +317,8 @@ function AttachmentModal({
     }, []);
 
     const validateAndDisplayFileToUpload = useCallback(
-        (data: FileObject) => {
-            if (!isDirectoryCheck(data)) {
+        (data: FileObject | undefined) => {
+            if (!data || !isDirectoryCheck(data)) {
                 return;
             }
             let fileObject = data;
@@ -617,4 +617,4 @@ export default withOnyx<AttachmentModalProps, AttachmentModalOnyxProps>({
     },
 })(memo(AttachmentModal));
 
-export type {Attachment};
+export type {Attachment, FileObject};
diff --git a/src/libs/API/parameters/AddCommentOrAttachementParams.ts b/src/libs/API/parameters/AddCommentOrAttachementParams.ts
index 58faf9fdfc9c..4eab35be7dd2 100644
--- a/src/libs/API/parameters/AddCommentOrAttachementParams.ts
+++ b/src/libs/API/parameters/AddCommentOrAttachementParams.ts
@@ -1,9 +1,11 @@
+import type {FileObject} from '@components/AttachmentModal';
+
 type AddCommentOrAttachementParams = {
     reportID: string;
     reportActionID?: string;
     commentReportActionID?: string | null;
     reportComment?: string;
-    file?: File;
+    file?: Partial<FileObject>;
     timezone?: string;
     shouldAllowActionableMentionWhispers?: boolean;
     clientCreatedTime?: string;
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 5f3efcbcdbb0..651cd1f6513d 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -8,6 +8,7 @@ import lodashIsEqual from 'lodash/isEqual';
 import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
 import Onyx from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
+import type {FileObject} from '@components/AttachmentModal';
 import * as Expensicons from '@components/Icon/Expensicons';
 import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars';
 import CONST from '@src/CONST';
@@ -1290,7 +1291,7 @@ function getRoomWelcomeMessage(report: OnyxEntry<Report>, isUserPolicyAdmin: boo
 /**
  * Returns true if Concierge is one of the chat participants (1:1 as well as group chats)
  */
-function chatIncludesConcierge(report: OnyxEntry<Report>): boolean {
+function chatIncludesConcierge(report: Partial<OnyxEntry<Report>>): boolean {
     return Boolean(report?.participantAccountIDs?.length && report?.participantAccountIDs?.includes(CONST.ACCOUNT_ID.CONCIERGE));
 }
 
@@ -2591,7 +2592,7 @@ function getParsedComment(text: string): string {
     return text.length <= CONST.MAX_MARKUP_LENGTH ? parser.replace(text) : lodashEscape(text);
 }
 
-function buildOptimisticAddCommentReportAction(text?: string, file?: File, actorAccountID?: number): OptimisticReportAction {
+function buildOptimisticAddCommentReportAction(text?: string, file?: Partial<FileObject>, actorAccountID?: number): OptimisticReportAction {
     const parser = new ExpensiMark();
     const commentText = getParsedComment(text ?? '');
     const isAttachment = !text && file !== undefined;
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 782cf2b174c2..a508325a3206 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -7,6 +7,7 @@ import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-nat
 import Onyx from 'react-native-onyx';
 import type {PartialDeep, ValueOf} from 'type-fest';
 import type {Emoji} from '@assets/emojis/types';
+import type {FileObject} from '@components/AttachmentModal';
 import * as ActiveClientManager from '@libs/ActiveClientManager';
 import * as API from '@libs/API';
 import type {
@@ -352,7 +353,7 @@ function notifyNewAction(reportID: string, accountID?: number, reportActionID?:
  * - Adding one attachment
  * - Add both a comment and attachment simultaneously
  */
-function addActions(reportID: string, text = '', file?: File) {
+function addActions(reportID: string, text = '', file?: Partial<FileObject>) {
     let reportCommentText = '';
     let reportCommentAction: OptimisticAddCommentReportAction | undefined;
     let attachmentAction: OptimisticAddCommentReportAction | undefined;
@@ -511,7 +512,7 @@ function addActions(reportID: string, text = '', file?: File) {
 }
 
 /** Add an attachment and optional comment. */
-function addAttachment(reportID: string, file: File, text = '') {
+function addAttachment(reportID: string, file: Partial<FileObject>, text = '') {
     addActions(reportID, text, file);
 }
 
diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
index 0dbfbba2759d..9511498ea699 100644
--- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
+++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
@@ -6,6 +6,7 @@ import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
 import type {SvgProps} from 'react-native-svg';
 import type {ValueOf} from 'type-fest';
+import type {FileObject} from '@components/AttachmentModal';
 import AttachmentPicker from '@components/AttachmentPicker';
 import Icon from '@components/Icon';
 import * as Expensicons from '@components/Icon/Expensicons';
@@ -46,7 +47,7 @@ type AttachmentPickerWithMenuItemsProps = {
     report: OnyxEntry<OnyxTypes.Report>;
 
     /** Callback to open the file in the modal */
-    displayFileInModal: (url: string) => void;
+    displayFileInModal: (url: FileObject | undefined) => void;
 
     /** Whether or not the full size composer is available */
     isFullComposerAvailable: boolean;
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index 074383897dac..76388372dd27 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -16,6 +16,7 @@ import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
 import type {useAnimatedRef} from 'react-native-reanimated';
 import type {Emoji} from '@assets/emojis/types';
+import type {FileObject} from '@components/AttachmentModal';
 import Composer from '@components/Composer';
 import useKeyboardState from '@hooks/useKeyboardState';
 import useLocalize from '@hooks/useLocalize';
@@ -85,7 +86,7 @@ type ComposerWithSuggestionsProps = {
     isComposerFullSize: boolean;
     isMenuVisible: boolean;
     inputPlaceholder: string;
-    displayFileInModal: (file: File | undefined) => void;
+    displayFileInModal: (file: FileObject | undefined) => void;
     textInputShouldClear: boolean;
     setTextInputShouldClear: (shouldClear: boolean) => void;
     isBlockedFromConcierge: boolean;
@@ -107,6 +108,8 @@ type ComposerWithSuggestionsProps = {
     lastReportAction?: OnyxTypes.ReportAction;
     includeChronos?: boolean;
     parentReportActionID?: string;
+    // eslint-disable-next-line react/no-unused-prop-types
+    parentReportID: string | undefined;
 } & ComposerWithSuggestionsOnyxProps &
     Partial<ChildrenProps>;
 
@@ -748,7 +751,7 @@ export default withOnyx<ComposerWithSuggestionsProps & RefAttributes<ComposerRef
         key: ONYXKEYS.INPUT_FOCUSED,
     },
     parentReportActions: {
-        key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`,
+        key: ({parentReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`,
         canEvict: false,
         initWithStoredValues: false,
     },
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index a5646f9c99a3..02d4105c3500 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -76,9 +76,6 @@ type ReportActionComposeProps = {
     /** The ID of the report actions will be created for */
     reportID: string;
 
-    /** Array of report actions for this report */
-    reportActions?: OnyxTypes.ReportAction[];
-
     /** The report currently being looked at */
     report: OnyxEntry<OnyxTypes.Report>;
 
@@ -97,9 +94,11 @@ type ReportActionComposeProps = {
     /** Whether the report is ready for display */
     isReportReadyForDisplay?: boolean;
 
+    /** Whether the chat is empty */
     isEmptyChat?: boolean;
 
-    lastReportAction?: any;
+    /** The last report action */
+    lastReportAction?: OnyxTypes.ReportAction;
 } & ReportActionComposeOnyxProps &
     WithCurrentUserPersonalDetailsProps;
 
@@ -118,7 +117,6 @@ function ReportActionCompose({
     pendingAction,
     report,
     reportID,
-    reportActions,
     listHeight = 0,
     shouldShowComposeInput = true,
     isReportReadyForDisplay = true,
@@ -269,7 +267,7 @@ function ReportActionCompose({
     }, []);
 
     const addAttachment = useCallback(
-        (file: FileObject) => {
+        (file: Partial<FileObject>) => {
             const newComment = composerRef.current?.prepareCommentAndResetComposer();
             Report.addAttachment(reportID, file, newComment);
             setTextInputShouldClear(false);
@@ -437,7 +435,7 @@ function ReportActionCompose({
                                         reportID={reportID}
                                         parentReportID={report?.parentReportID}
                                         parentReportActionID={report?.parentReportActionID}
-                                        includesChronos={ReportUtils.chatIncludesChronos(report)}
+                                        includeChronos={ReportUtils.chatIncludesChronos(report)}
                                         isEmptyChat={isEmptyChat}
                                         lastReportAction={lastReportAction}
                                         isMenuVisible={isMenuVisible}
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
index 591ee43ce6cd..1ba1689e7d6d 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
@@ -1,5 +1,4 @@
 import type {OnyxEntry} from 'react-native-onyx';
-import type {Report} from '@src/types/onyx';
 
 type SilentCommentUpdaterOnyxProps = {
     /** The comment of the report */
@@ -13,9 +12,6 @@ type SilentCommentUpdaterProps = {
     /** The ID of the report associated with the comment */
     reportID: string;
 
-    /** The report associated with the comment */
-    report: OnyxEntry<Report>;
-
     /** The value of the comment */
     value: string;
 
diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts
index d2f0afad5b7a..59fb785234eb 100644
--- a/src/types/onyx/ReportAction.ts
+++ b/src/types/onyx/ReportAction.ts
@@ -1,4 +1,5 @@
 import type {ValueOf} from 'type-fest';
+import type {FileObject} from '@components/AttachmentModal';
 import type {AvatarSource} from '@libs/UserUtils';
 import type CONST from '@src/CONST';
 import type {EmptyObject} from '@src/types/utils/EmptyObject';
@@ -165,7 +166,7 @@ type ReportActionBase = {
     isFirstItem?: boolean;
 
     /** Informations about attachments of report action */
-    attachmentInfo?: File | EmptyObject;
+    attachmentInfo?: Partial<FileObject> | EmptyObject;
 
     /** Receipt tied to report action */
     receipt?: Receipt;

From 61ad4769e0215b48706a74615e843fb765eeab58 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Fri, 2 Feb 2024 18:51:38 +0100
Subject: [PATCH 22/29] fix: resolve comments

---
 src/components/Composer/types.ts              |  4 +-
 src/components/LocaleContextProvider.tsx      |  4 +-
 src/libs/ComposerUtils/index.ts               |  2 +-
 src/libs/DateUtils.ts                         |  2 +-
 src/libs/E2E/tests/chatOpeningTest.e2e.ts     |  5 +-
 src/libs/E2E/tests/reportTypingTest.e2e.ts    |  5 +-
 src/libs/EmojiUtils.ts                        |  7 +-
 .../home/report/ParticipantLocalTime.tsx      |  4 +-
 .../AttachmentPickerWithMenuItems.tsx         |  2 +-
 .../ComposerWithSuggestions.tsx               | 73 +++++++++++++++++--
 .../ComposerWithSuggestions/index.e2e.tsx     |  8 +-
 .../ReportActionCompose.tsx                   | 46 ++++++------
 .../report/ReportActionCompose/SendButton.tsx |  2 -
 .../SilentCommentUpdater/index.android.tsx    |  2 +-
 .../SilentCommentUpdater/index.tsx            |  4 +-
 .../SilentCommentUpdater/types.ts             |  6 +-
 .../ReportActionCompose/SuggestionEmoji.tsx   | 13 ++--
 .../ReportActionCompose/SuggestionMention.tsx | 10 +--
 .../ReportActionCompose/Suggestions.tsx       | 21 ++++++
 .../home/report/ReportTypingIndicator.tsx     |  4 +-
 20 files changed, 147 insertions(+), 77 deletions(-)

diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts
index 9565eaf6208f..f19feb94dd0a 100644
--- a/src/components/Composer/types.ts
+++ b/src/components/Composer/types.ts
@@ -5,7 +5,7 @@ type TextSelection = {
     end?: number;
 };
 
-type ComposerProps = {
+type ComposerProps = TextInputProps & {
     /** identify id in the text input */
     id?: string;
 
@@ -76,6 +76,6 @@ type ComposerProps = {
 
     /** Should make the input only scroll inside the element avoid scroll out to parent */
     shouldContainScroll?: boolean;
-} & TextInputProps;
+};
 
 export type {TextSelection, ComposerProps};
diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx
index 25b468181b87..6d819f4d6eaa 100644
--- a/src/components/LocaleContextProvider.tsx
+++ b/src/components/LocaleContextProvider.tsx
@@ -45,7 +45,7 @@ type LocaleContextProps = {
 
     /** Returns a locally converted phone number for numbers from the same region
      * and an internationally converted phone number with the country code for numbers from other regions */
-    formatPhoneNumber: (phoneNumber: string) => string;
+    formatPhoneNumber: (phoneNumber: string | undefined) => string;
 
     /** Gets the locale digit corresponding to a standard digit */
     toLocaleDigit: (digit: string) => string;
@@ -94,7 +94,7 @@ function LocaleContextProvider({preferredLocale, currentUserPersonalDetails = {}
 
     const updateLocale = useMemo<LocaleContextProps['updateLocale']>(() => () => DateUtils.setLocale(locale), [locale]);
 
-    const formatPhoneNumber = useMemo<LocaleContextProps['formatPhoneNumber']>(() => (phoneNumber) => LocalePhoneNumber.formatPhoneNumber(phoneNumber), []);
+    const formatPhoneNumber = useMemo<LocaleContextProps['formatPhoneNumber']>(() => (phoneNumber) => LocalePhoneNumber.formatPhoneNumber(phoneNumber ?? ''), []);
 
     const toLocaleDigit = useMemo<LocaleContextProps['toLocaleDigit']>(() => (digit) => LocaleDigitUtils.toLocaleDigit(locale, digit), [locale]);
 
diff --git a/src/libs/ComposerUtils/index.ts b/src/libs/ComposerUtils/index.ts
index de937c0ba915..6018cad86e47 100644
--- a/src/libs/ComposerUtils/index.ts
+++ b/src/libs/ComposerUtils/index.ts
@@ -18,7 +18,7 @@ function insertText(text: string, selection: Selection, textToInsert: string): s
  * Insert a white space at given index of text
  * @param text - text that needs whitespace to be appended to
  */
-function insertWhiteSpaceAtIndex(text: string | null, index: number) {
+function insertWhiteSpaceAtIndex(text: string, index: number) {
     return `${text?.slice(0, index)} ${text?.slice(index)}`;
 }
 
diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts
index 9cb08556f082..c10d6b90128b 100644
--- a/src/libs/DateUtils.ts
+++ b/src/libs/DateUtils.ts
@@ -265,7 +265,7 @@ function formatToLongDateWithWeekday(datetime: string | Date): string {
  *
  * @returns Sunday
  */
-function formatToDayOfWeek(datetime: string): string {
+function formatToDayOfWeek(datetime: Date): string {
     return format(new Date(datetime), CONST.DATE.WEEKDAY_TIME_FORMAT);
 }
 
diff --git a/src/libs/E2E/tests/chatOpeningTest.e2e.ts b/src/libs/E2E/tests/chatOpeningTest.e2e.ts
index 2e2ce76b348d..17d9dfa1cb4d 100644
--- a/src/libs/E2E/tests/chatOpeningTest.e2e.ts
+++ b/src/libs/E2E/tests/chatOpeningTest.e2e.ts
@@ -2,18 +2,17 @@ import type {NativeConfig} from 'react-native-config';
 import E2ELogin from '@libs/E2E/actions/e2eLogin';
 import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded';
 import E2EClient from '@libs/E2E/client';
-import type {TestConfig} from '@libs/E2E/types';
 import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow';
 import Navigation from '@libs/Navigation/Navigation';
 import Performance from '@libs/Performance';
 import CONST from '@src/CONST';
 import ROUTES from '@src/ROUTES';
 
-const test = (config: TestConfig) => {
+const test = (config: NativeConfig) => {
     // check for login (if already logged in the action will simply resolve)
     console.debug('[E2E] Logging in for chat opening');
 
-    const reportID = getConfigValueOrThrow('reportID', config as NativeConfig);
+    const reportID = getConfigValueOrThrow('reportID', config);
 
     E2ELogin().then((neededLogin) => {
         if (neededLogin) {
diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.ts b/src/libs/E2E/tests/reportTypingTest.e2e.ts
index 17464f7eb8d6..817bda941611 100644
--- a/src/libs/E2E/tests/reportTypingTest.e2e.ts
+++ b/src/libs/E2E/tests/reportTypingTest.e2e.ts
@@ -4,7 +4,6 @@ import E2ELogin from '@libs/E2E/actions/e2eLogin';
 import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded';
 import waitForKeyboard from '@libs/E2E/actions/waitForKeyboard';
 import E2EClient from '@libs/E2E/client';
-import type {TestConfig} from '@libs/E2E/types';
 import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow';
 import Navigation from '@libs/Navigation/Navigation';
 import Performance from '@libs/Performance';
@@ -13,11 +12,11 @@ import CONST from '@src/CONST';
 import ROUTES from '@src/ROUTES';
 import * as NativeCommands from '../../../../tests/e2e/nativeCommands/NativeCommandsAction';
 
-const test = (config: TestConfig) => {
+const test = (config: NativeConfig) => {
     // check for login (if already logged in the action will simply resolve)
     console.debug('[E2E] Logging in for typing');
 
-    const reportID = getConfigValueOrThrow('reportID', config as NativeConfig);
+    const reportID = getConfigValueOrThrow('reportID', config);
 
     E2ELogin().then((neededLogin) => {
         if (neededLogin) {
diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts
index a7063e204efe..819aecf09d43 100644
--- a/src/libs/EmojiUtils.ts
+++ b/src/libs/EmojiUtils.ts
@@ -10,7 +10,6 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import type {FrequentlyUsedEmoji, Locale} from '@src/types/onyx';
 import type {ReportActionReaction, UsersReactions} from '@src/types/onyx/ReportActionReactions';
 import type IconAsset from '@src/types/utils/IconAsset';
-import type {SupportedLanguage} from './EmojiTrie';
 
 type HeaderIndice = {code: string; index: number; icon: IconAsset};
 type EmojiSpacer = {code: string; spacer: boolean};
@@ -370,12 +369,12 @@ function replaceEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEF
 /**
  * Find all emojis in a text and replace them with their code.
  */
-function replaceAndExtractEmojis(text: string | null, preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji {
+function replaceAndExtractEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji {
     const {text: convertedText = '', emojis = [], cursorPosition} = replaceEmojis(text ?? '', preferredSkinTone, lang);
 
     return {
         text: convertedText,
-        emojis: emojis.concat(extractEmojis(text ?? '')),
+        emojis: emojis.concat(extractEmojis(text)),
         cursorPosition,
     };
 }
@@ -384,7 +383,7 @@ function replaceAndExtractEmojis(text: string | null, preferredSkinTone: number
  * Suggest emojis when typing emojis prefix after colon
  * @param [limit] - matching emojis limit
  */
-function suggestEmojis(text: string, lang: SupportedLanguage, limit: number = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined {
+function suggestEmojis(text: string, lang: Locale, limit: number = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined {
     // emojisTrie is importing the emoji JSON file on the app starting and we want to avoid it
     const emojisTrie = require('./EmojiTrie').default;
 
diff --git a/src/pages/home/report/ParticipantLocalTime.tsx b/src/pages/home/report/ParticipantLocalTime.tsx
index e97fad8f20f0..58d85c0662b2 100644
--- a/src/pages/home/report/ParticipantLocalTime.tsx
+++ b/src/pages/home/report/ParticipantLocalTime.tsx
@@ -22,8 +22,8 @@ function getParticipantLocalTime(participant: PersonalDetails, preferredLocale:
     const reportRecipientTimezone = participant.timezone || CONST.DEFAULT_TIME_ZONE;
     const reportTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale ?? CONST.LOCALES.DEFAULT, undefined, reportRecipientTimezone.selected);
     const currentTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale ?? CONST.LOCALES.DEFAULT);
-    const reportRecipientDay = DateUtils.formatToDayOfWeek(reportTimezone.toDateString());
-    const currentUserDay = DateUtils.formatToDayOfWeek(currentTimezone.toDateString());
+    const reportRecipientDay = DateUtils.formatToDayOfWeek(reportTimezone);
+    const currentUserDay = DateUtils.formatToDayOfWeek(currentTimezone);
     if (reportRecipientDay !== currentUserDay) {
         return `${DateUtils.formatToLocalTime(reportTimezone)} ${reportRecipientDay}`;
     }
diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
index 9511498ea699..e6a75e6f157b 100644
--- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
+++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
@@ -194,7 +194,7 @@ function AttachmentPickerWithMenuItems({
 
     return (
         <AttachmentPicker>
-            {/* @ts-expect-error TODO: Remove this once SettlementButton (https://github.com/Expensify/App/issues/25134) is migrated to TypeScript. */}
+            {/* @ts-expect-error TODO: Remove this once AttachmentPicker (https://github.com/Expensify/App/issues/25134) is migrated to TypeScript. */}
             {({openPicker}) => {
                 const triggerAttachmentPicker = () => {
                     onTriggerAttachmentPicker();
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index 76388372dd27..96989b2a647b 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -1,6 +1,6 @@
 import {useIsFocused, useNavigation} from '@react-navigation/native';
 import lodashDebounce from 'lodash/debounce';
-import type {ForwardedRef, RefAttributes, RefObject} from 'react';
+import type {ForwardedRef, MutableRefObject, RefAttributes, RefObject} from 'react';
 import React, {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
 import type {
     LayoutChangeEvent,
@@ -79,35 +79,94 @@ type ComposerWithSuggestionsOnyxProps = {
 };
 
 type ComposerWithSuggestionsProps = {
+    /** Report ID */
     reportID: string;
+
+    /** Callback to focus composer */
     onFocus: () => void;
+
+    /** Callback to blur composer */
     onBlur: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
+
+    /** Callback to update the value of the composer */
     onValueChange: (value: string) => void;
+
+    /** Whether the composer is full size */
     isComposerFullSize: boolean;
+
+    /** Whether the menu is visible */
     isMenuVisible: boolean;
+
+    /** The placeholder for the input */
     inputPlaceholder: string;
+
+    /** Function to display a file in a modal */
     displayFileInModal: (file: FileObject | undefined) => void;
+
+    /** Whether the text input should clear */
     textInputShouldClear: boolean;
+
+    /** Function to set the text input should clear */
     setTextInputShouldClear: (shouldClear: boolean) => void;
+
+    /** Whether the user is blocked from concierge */
     isBlockedFromConcierge: boolean;
+
+    /** Whether the input is disabled */
     disabled: boolean;
+
+    /** Whether the full composer is available */
     isFullComposerAvailable: boolean;
+
+    /** Function to set whether the full composer is available */
     setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void;
+
+    /** Function to set whether the comment is empty */
     setIsCommentEmpty: (isCommentEmpty: boolean) => void;
+
+    /** Function to handle sending a message */
     handleSendMessage: () => void;
+
+    /** Whether the compose input should show */
     shouldShowComposeInput: OnyxEntry<boolean>;
+
+    /** Function to measure the parent container */
     measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void;
+
+    /** The height of the list */
     listHeight: number;
+
+    /** Whether the scroll is likely to trigger a layout */
     isScrollLikelyLayoutTriggered: RefObject<boolean>;
+
+    /** Function to raise the scroll is likely layout triggered */
     raiseIsScrollLikelyLayoutTriggered: () => void;
+
+    /** The ref to the suggestions */
     suggestionsRef: React.RefObject<SuggestionsRef>;
+
+    /** The ref to the animated input */
     animatedRef: AnimatedRef;
-    isNextModalWillOpenRef: RefObject<boolean | null>;
+
+    /** The ref to the next modal will open */
+    isNextModalWillOpenRef: MutableRefObject<boolean | null>;
+
+    /** Whether the edit is focused */
     editFocused: boolean;
+
+    /** Wheater chat is empty */
     isEmptyChat?: boolean;
+
+    /** The last report action */
     lastReportAction?: OnyxTypes.ReportAction;
+
+    /** Whether to include chronos */
     includeChronos?: boolean;
+
+    /** The parent report action ID */
     parentReportActionID?: string;
+
+    /** The parent report ID */
     // eslint-disable-next-line react/no-unused-prop-types
     parentReportID: string | undefined;
 } & ComposerWithSuggestionsOnyxProps &
@@ -275,13 +334,13 @@ function ComposerWithSuggestions(
      * @property diff - The newly added characters.
      */
     const findNewlyAddedChars = useCallback(
-        (prevText: string, newText: string | null): NewlyAddedChars => {
+        (prevText: string, newText: string): NewlyAddedChars => {
             let startIndex = -1;
             let endIndex = -1;
             let currentIndex = 0;
 
             // Find the first character mismatch with newText
-            while (currentIndex < (newText?.length ?? 0) && prevText.charAt(currentIndex) === newText?.charAt(currentIndex) && selection.start > currentIndex) {
+            while (currentIndex < newText.length && prevText.charAt(currentIndex) === newText?.charAt(currentIndex) && selection.start > currentIndex) {
                 currentIndex++;
             }
 
@@ -295,7 +354,6 @@ function ComposerWithSuggestions(
                     endIndex = currentIndex + (newText?.length ?? 0);
                 }
             }
-
             return {
                 startIndex,
                 endIndex,
@@ -309,7 +367,7 @@ function ComposerWithSuggestions(
      * Update the value of the comment in Onyx
      */
     const updateComment = useCallback(
-        (commentValue: string | null, shouldDebounceSaveComment?: boolean) => {
+        (commentValue: string, shouldDebounceSaveComment?: boolean) => {
             raiseIsScrollLikelyLayoutTriggered();
             const {startIndex, endIndex, diff} = findNewlyAddedChars(lastTextRef.current, commentValue);
             const isEmojiInserted = diff.length && endIndex > startIndex && diff.trim() === diff && EmojiUtils.containsOnlyEmojis(diff);
@@ -317,7 +375,7 @@ function ComposerWithSuggestions(
                 text: newComment,
                 emojis,
                 cursorPosition,
-            } = EmojiUtils.replaceAndExtractEmojis(isEmojiInserted ? ComposerUtils.insertWhiteSpaceAtIndex(commentValue, endIndex) : commentValue, preferredSkinTone, preferredLocale);
+            } = EmojiUtils.replaceAndExtractEmojis(isEmojiInserted ? ComposerUtils.insertWhiteSpaceAtIndex(commentValue, endIndex) : commentValue ?? '', preferredSkinTone, preferredLocale);
             if (emojis.length) {
                 const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current);
                 if (newEmojis.length) {
@@ -595,7 +653,6 @@ function ComposerWithSuggestions(
     const prevIsFocused = usePrevious(isFocused);
     useEffect(() => {
         if (modal?.isVisible && !prevIsModalVisible) {
-            // @ts-expect-error need to reassign this ref
             // eslint-disable-next-line no-param-reassign
             isNextModalWillOpenRef.current = false;
         }
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
index 94e65a48e46b..0fa535329d33 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
@@ -1,4 +1,4 @@
-import type {ForwardedRef, RefObject} from 'react';
+import type {ForwardedRef} from 'react';
 import React, {forwardRef, useEffect} from 'react';
 import E2EClient from '@libs/E2E/client';
 import type {ComposerRef} from '@pages/home/report/ReportActionCompose/ReportActionCompose';
@@ -20,17 +20,17 @@ function ComposerWithSuggestionsE2e(props: ComposerWithSuggestionsProps, ref: Fo
     // Eventually Auto focus on e2e tests
     useEffect(() => {
         const testConfig = E2EClient.getCurrentActiveTestConfig();
-        if (testConfig?.reportScreen && typeof testConfig.reportScreen !== 'string' && (testConfig?.reportScreen.autoFocus ?? false) === false) {
+        if (testConfig?.reportScreen && typeof testConfig.reportScreen !== 'string' && !testConfig?.reportScreen.autoFocus) {
             return;
         }
 
         // We need to wait for the component to be mounted before focusing
         setTimeout(() => {
-            if (!(ref as RefObject<ComposerRef>)?.current) {
+            if (!(ref && 'current' in ref)) {
                 return;
             }
 
-            (ref as RefObject<ComposerRef>).current?.focus(true);
+            ref.current?.focus(true);
         }, 1);
     }, [ref]);
 
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 02d4105c3500..98630ff88b23 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -69,38 +69,38 @@ type ReportActionComposeOnyxProps = {
     shouldShowComposeInput: OnyxEntry<boolean>;
 };
 
-type ReportActionComposeProps = {
-    /** A method to call when the form is submitted */
-    onSubmit: (newComment: string | undefined) => void;
+type ReportActionComposeProps = ReportActionComposeOnyxProps &
+    WithCurrentUserPersonalDetailsProps & {
+        /** A method to call when the form is submitted */
+        onSubmit: (newComment: string | undefined) => void;
 
-    /** The ID of the report actions will be created for */
-    reportID: string;
+        /** The ID of the report actions will be created for */
+        reportID: string;
 
-    /** The report currently being looked at */
-    report: OnyxEntry<OnyxTypes.Report>;
+        /** The report currently being looked at */
+        report: OnyxEntry<OnyxTypes.Report>;
 
-    /** Is composer full size */
-    isComposerFullSize?: boolean;
+        /** Is composer full size */
+        isComposerFullSize?: boolean;
 
-    /** Whether user interactions should be disabled */
-    disabled?: boolean;
+        /** Whether user interactions should be disabled */
+        disabled?: boolean;
 
-    /** Height of the list which the composer is part of */
-    listHeight?: number;
+        /** Height of the list which the composer is part of */
+        listHeight?: number;
 
-    /** The type of action that's pending  */
-    pendingAction?: OnyxCommon.PendingAction;
+        /** The type of action that's pending  */
+        pendingAction?: OnyxCommon.PendingAction;
 
-    /** Whether the report is ready for display */
-    isReportReadyForDisplay?: boolean;
+        /** Whether the report is ready for display */
+        isReportReadyForDisplay?: boolean;
 
-    /** Whether the chat is empty */
-    isEmptyChat?: boolean;
+        /** Whether the chat is empty */
+        isEmptyChat?: boolean;
 
-    /** The last report action */
-    lastReportAction?: OnyxTypes.ReportAction;
-} & ReportActionComposeOnyxProps &
-    WithCurrentUserPersonalDetailsProps;
+        /** The last report action */
+        lastReportAction?: OnyxTypes.ReportAction;
+    };
 
 // We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will
 // prevent auto focus on existing chat for mobile device
diff --git a/src/pages/home/report/ReportActionCompose/SendButton.tsx b/src/pages/home/report/ReportActionCompose/SendButton.tsx
index feeff49ceb55..4726c1638f42 100644
--- a/src/pages/home/report/ReportActionCompose/SendButton.tsx
+++ b/src/pages/home/report/ReportActionCompose/SendButton.tsx
@@ -46,8 +46,6 @@ function SendButton({isDisabled: isDisabledProp, handleSendMessage}: SendButtonP
                         ]}
                         role={CONST.ROLE.BUTTON}
                         accessibilityLabel={translate('common.send')}
-                        accessible
-                        onPress={() => {}}
                     >
                         {({pressed}) => (
                             <Icon
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx
index cee9cbf794bf..e33080d971f2 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx
@@ -15,7 +15,7 @@ import type {SilentCommentUpdaterOnyxProps, SilentCommentUpdaterProps} from './t
  */
 function SilentCommentUpdater({comment = '', updateComment}: SilentCommentUpdaterProps) {
     useEffect(() => {
-        updateComment(comment);
+        updateComment(comment ?? '');
         // eslint-disable-next-line react-hooks/exhaustive-deps -- We need to run this on mount
     }, []);
 
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
index a1d591c97297..c84bd3786610 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
@@ -17,7 +17,7 @@ function SilentCommentUpdater({comment, commentRef, reportID, value, updateComme
     const prevPreferredLocale = usePrevious(preferredLocale);
 
     useEffect(() => {
-        updateComment(comment ?? null);
+        updateComment(comment ?? '');
         // eslint-disable-next-line react-hooks/exhaustive-deps -- We need to run this on mount
     }, []);
 
@@ -32,7 +32,7 @@ function SilentCommentUpdater({comment, commentRef, reportID, value, updateComme
             return;
         }
 
-        updateComment(comment);
+        updateComment(comment ?? '');
     }, [prevCommentProp, prevPreferredLocale, prevReportId, comment, preferredLocale, reportID, updateComment, value, commentRef]);
 
     return null;
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
index 1ba1689e7d6d..dbc23b0279c3 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/types.ts
@@ -5,9 +5,9 @@ type SilentCommentUpdaterOnyxProps = {
     comment: OnyxEntry<string>;
 };
 
-type SilentCommentUpdaterProps = {
+type SilentCommentUpdaterProps = SilentCommentUpdaterOnyxProps & {
     /** Updates the comment */
-    updateComment: (comment: OnyxEntry<string>) => void;
+    updateComment: (comment: string) => void;
 
     /** The ID of the report associated with the comment */
     reportID: string;
@@ -17,6 +17,6 @@ type SilentCommentUpdaterProps = {
 
     /** The ref of the comment */
     commentRef: React.RefObject<string>;
-} & SilentCommentUpdaterOnyxProps;
+};
 
 export type {SilentCommentUpdaterProps, SilentCommentUpdaterOnyxProps};
diff --git a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
index d343616a206d..206c91ed9b08 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
+++ b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
@@ -6,7 +6,6 @@ import type {Emoji} from '@assets/emojis/types';
 import EmojiSuggestions from '@components/EmojiSuggestions';
 import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
 import useLocalize from '@hooks/useLocalize';
-import type {SupportedLanguage} from '@libs/EmojiTrie';
 import * as EmojiUtils from '@libs/EmojiUtils';
 import * as SuggestionsUtils from '@libs/SuggestionUtils';
 import CONST from '@src/CONST';
@@ -26,11 +25,11 @@ type SuggestionEmojiOnyxProps = {
     preferredSkinTone: number;
 };
 
-type SuggestionEmojiProps = {
-    /** Function to clear the input */
-    resetKeyboardInput: (() => void) | undefined;
-} & SuggestionEmojiOnyxProps &
-    SuggestionProps;
+type SuggestionEmojiProps = SuggestionProps &
+    SuggestionEmojiOnyxProps & {
+        /** Function to clear the input */
+        resetKeyboardInput: (() => void) | undefined;
+    };
 
 /**
  * Check if this piece of string looks like an emoji
@@ -166,7 +165,7 @@ function SuggestionEmoji(
                 colonIndex,
                 shouldShowSuggestionMenu: false,
             };
-            const newSuggestedEmojis = EmojiUtils.suggestEmojis(leftString, preferredLocale as SupportedLanguage);
+            const newSuggestedEmojis = EmojiUtils.suggestEmojis(leftString, preferredLocale);
 
             if (newSuggestedEmojis?.length && isCurrentlyShowingEmojiSuggestion) {
                 nextState.suggestedEmojis = newSuggestedEmojis;
diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
index 02bcd27093e7..1e3e80ad9d7e 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
+++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
@@ -15,8 +15,6 @@ import type {PersonalDetailsList} from '@src/types/onyx';
 import type {SuggestionsRef} from './ReportActionCompose';
 import type {SuggestionProps} from './Suggestions';
 
-type SuggestionMentionProps = {isAutoSuggestionPickerLarge: boolean} & SuggestionProps;
-
 type SuggestionValues = {
     suggestedMentions: Mention[];
     atSignIndex: number;
@@ -37,7 +35,7 @@ const defaultSuggestionsValues: SuggestionValues = {
 };
 
 function SuggestionMention(
-    {value, selection, setSelection, updateComment, isAutoSuggestionPickerLarge, measureParentContainer, isComposerFocused}: SuggestionMentionProps,
+    {value, selection, setSelection, updateComment, isAutoSuggestionPickerLarge, measureParentContainer, isComposerFocused}: SuggestionProps,
     ref: ForwardedRef<SuggestionsRef>,
 ) {
     const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT;
@@ -161,7 +159,7 @@ function SuggestionMention(
             sortedPersonalDetails.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS - suggestions.length).forEach((detail) => {
                 suggestions.push({
                     text: PersonalDetailsUtils.getDisplayNameOrDefault(detail),
-                    alternateText: formatPhoneNumber(detail?.login ?? ''),
+                    alternateText: formatPhoneNumber(detail?.login),
                     login: detail?.login,
                     icons: [
                         {
@@ -200,7 +198,7 @@ function SuggestionMention(
 
             const leftString = value.substring(0, suggestionEndIndex);
             const words = leftString.split(CONST.REGEX.SPACE_OR_EMOJI);
-            const lastWord = words.at(-1) ?? '';
+            const lastWord: string = words.at(-1) ?? '';
             const secondToLastWord = words[words.length - 3];
 
             let atSignIndex;
@@ -297,7 +295,7 @@ function SuggestionMention(
             mentions={suggestionValues.suggestedMentions}
             prefix={suggestionValues.mentionPrefix}
             onSelect={insertSelectedMention}
-            isMentionPickerLarge={isAutoSuggestionPickerLarge}
+            isMentionPickerLarge={!!isAutoSuggestionPickerLarge}
             measureParentContainer={measureParentContainer}
         />
     );
diff --git a/src/pages/home/report/ReportActionCompose/Suggestions.tsx b/src/pages/home/report/ReportActionCompose/Suggestions.tsx
index 0968a61c3abb..61026a792919 100644
--- a/src/pages/home/report/ReportActionCompose/Suggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/Suggestions.tsx
@@ -14,16 +14,37 @@ type Selection = {
 };
 
 type SuggestionProps = {
+    /** The current input value */
     value: string;
+
+    /** Callback to update the current input value */
     setValue: (newValue: string) => void;
+
+    /** The current selection value */
     selection: Selection;
+
+    /** Callback to update the current selection */
     setSelection: (newSelection: Selection) => void;
+
+    /** Callback to update the comment draft */
     updateComment: (newComment: string, shouldDebounceSaveComment?: boolean) => void;
+
+    /** Meaures the parent container's position and dimensions. */
     measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void;
+
+    /** Whether the composer is expanded */
     isComposerFullSize: boolean;
+
+    /** Report composer focus state */
     isComposerFocused?: boolean;
+
+    /** Callback to reset the keyboard input */
     resetKeyboardInput?: () => void;
+
+    /** Whether the auto suggestion picker is large */
     isAutoSuggestionPickerLarge?: boolean;
+
+    /** The height of the composer */
     composerHeight?: number;
 };
 
diff --git a/src/pages/home/report/ReportTypingIndicator.tsx b/src/pages/home/report/ReportTypingIndicator.tsx
index 6b00a4520295..e484ce9886a7 100755
--- a/src/pages/home/report/ReportTypingIndicator.tsx
+++ b/src/pages/home/report/ReportTypingIndicator.tsx
@@ -15,10 +15,10 @@ type ReportTypingIndicatorOnyxProps = {
     userTypingStatuses: OnyxEntry<ReportUserIsTyping>;
 };
 
-type ReportTypingIndicatorProps = {
+type ReportTypingIndicatorProps = ReportTypingIndicatorOnyxProps & {
     // eslint-disable-next-line react/no-unused-prop-types -- This is used by withOnyx
     reportID: string;
-} & ReportTypingIndicatorOnyxProps;
+};
 
 function ReportTypingIndicator({userTypingStatuses}: ReportTypingIndicatorProps) {
     const {translate} = useLocalize();

From fad94907fbdbd62dc75a4668055132d0dec9825b Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Mon, 5 Feb 2024 12:02:47 +0100
Subject: [PATCH 23/29] fix: resolve comments

---
 .../ReportActionCompose/SuggestionMention.tsx | 21 +++++--------------
 1 file changed, 5 insertions(+), 16 deletions(-)

diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
index 1e3e80ad9d7e..2b5080e788ab 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
+++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
@@ -1,3 +1,4 @@
+import lodashSortBy from 'lodash/sortBy';
 import type {ForwardedRef} from 'react';
 import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
 import * as Expensicons from '@components/Icon/Expensicons';
@@ -142,20 +143,8 @@ function SuggestionMention(
                 return true;
             });
 
-            const sortedPersonalDetails = filteredPersonalDetails.sort((a, b) => {
-                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Disabling this line for safeness as nullish coalescing works only if the value is undefined or null
-                const nameA = a?.displayName || a?.login || '';
-                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Disabling this line for safeness as nullish coalescing works only if the value is undefined or null
-                const nameB = b?.displayName || b?.login || '';
-
-                if (nameA < nameB) {
-                    return -1;
-                }
-                if (nameA > nameB) {
-                    return 1;
-                }
-                return 0;
-            });
+            // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing cannot be used if left side can be empty string
+            const sortedPersonalDetails = lodashSortBy(filteredPersonalDetails, (detail) => detail?.displayName || detail?.login);
             sortedPersonalDetails.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS - suggestions.length).forEach((detail) => {
                 suggestions.push({
                     text: PersonalDetailsUtils.getDisplayNameOrDefault(detail),
@@ -201,9 +190,9 @@ function SuggestionMention(
             const lastWord: string = words.at(-1) ?? '';
             const secondToLastWord = words[words.length - 3];
 
-            let atSignIndex;
+            let atSignIndex: number | undefined;
             let suggestionWord = '';
-            let prefix;
+            let prefix: string;
 
             // Detect if the last two words contain a mention (two words are needed to detect a mention with a space in it)
             if (lastWord.startsWith('@')) {

From 5735f6163034bff9ad5b24adfbda21caaa132b9c Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Thu, 8 Feb 2024 18:40:53 +0100
Subject: [PATCH 24/29] fix: resolve comments

---
 src/components/AttachmentModal.tsx            |   4 +-
 src/components/Composer/types.ts              |   2 +-
 src/components/LocaleContextProvider.tsx      |   2 +-
 src/libs/ComposerUtils/index.ts               |   2 +-
 src/libs/EmojiUtils.ts                        |   2 +-
 .../home/report/ParticipantLocalTime.tsx      |  13 +-
 .../AttachmentPickerWithMenuItems.tsx         |  21 +--
 .../ComposerWithSuggestions.tsx               | 146 +++++++++---------
 .../ComposerWithSuggestions/index.e2e.tsx     |   2 +-
 .../ReportActionCompose.tsx                   |   4 +-
 .../report/ReportActionCompose/SendButton.tsx |   9 +-
 .../SilentCommentUpdater/index.android.tsx    |   2 +-
 .../ReportActionCompose/SuggestionEmoji.tsx   |   2 +-
 .../home/report/ReportTypingIndicator.tsx     |   2 +-
 14 files changed, 99 insertions(+), 114 deletions(-)

diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx
index adf5b7b232a2..f97da9fcd9b7 100755
--- a/src/components/AttachmentModal.tsx
+++ b/src/components/AttachmentModal.tsx
@@ -80,7 +80,7 @@ type ImagePickerResponse = {
 type FileObject = File | ImagePickerResponse;
 
 type ChildrenProps = {
-    displayFileInModal: (data: FileObject | undefined) => void;
+    displayFileInModal: (data: FileObject) => void;
     show: () => void;
 };
 
@@ -317,7 +317,7 @@ function AttachmentModal({
     }, []);
 
     const validateAndDisplayFileToUpload = useCallback(
-        (data: FileObject | undefined) => {
+        (data: FileObject) => {
             if (!data || !isDirectoryCheck(data)) {
                 return;
             }
diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts
index f19feb94dd0a..6bc44aba69cd 100644
--- a/src/components/Composer/types.ts
+++ b/src/components/Composer/types.ts
@@ -31,7 +31,7 @@ type ComposerProps = TextInputProps & {
     onNumberOfLinesChange?: (numberOfLines: number) => void;
 
     /** Callback method to handle pasting a file */
-    onPasteFile?: (file?: File) => void;
+    onPasteFile?: (file: File) => void;
 
     /** General styles to apply to the text input */
     // eslint-disable-next-line react/forbid-prop-types
diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx
index 6d819f4d6eaa..eca433025f71 100644
--- a/src/components/LocaleContextProvider.tsx
+++ b/src/components/LocaleContextProvider.tsx
@@ -94,7 +94,7 @@ function LocaleContextProvider({preferredLocale, currentUserPersonalDetails = {}
 
     const updateLocale = useMemo<LocaleContextProps['updateLocale']>(() => () => DateUtils.setLocale(locale), [locale]);
 
-    const formatPhoneNumber = useMemo<LocaleContextProps['formatPhoneNumber']>(() => (phoneNumber) => LocalePhoneNumber.formatPhoneNumber(phoneNumber ?? ''), []);
+    const formatPhoneNumber = useMemo<LocaleContextProps['formatPhoneNumber']>(() => (phoneNumber) => LocalePhoneNumber.formatPhoneNumber(phoneNumber), []);
 
     const toLocaleDigit = useMemo<LocaleContextProps['toLocaleDigit']>(() => (digit) => LocaleDigitUtils.toLocaleDigit(locale, digit), [locale]);
 
diff --git a/src/libs/ComposerUtils/index.ts b/src/libs/ComposerUtils/index.ts
index 6018cad86e47..94bba5d0d00c 100644
--- a/src/libs/ComposerUtils/index.ts
+++ b/src/libs/ComposerUtils/index.ts
@@ -19,7 +19,7 @@ function insertText(text: string, selection: Selection, textToInsert: string): s
  * @param text - text that needs whitespace to be appended to
  */
 function insertWhiteSpaceAtIndex(text: string, index: number) {
-    return `${text?.slice(0, index)} ${text?.slice(index)}`;
+    return `${text.slice(0, index)} ${text.slice(index)}`;
 }
 
 /**
diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts
index 05a1cb29141b..cab0f48d75fd 100644
--- a/src/libs/EmojiUtils.ts
+++ b/src/libs/EmojiUtils.ts
@@ -370,7 +370,7 @@ function replaceEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEF
  * Find all emojis in a text and replace them with their code.
  */
 function replaceAndExtractEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji {
-    const {text: convertedText = '', emojis = [], cursorPosition} = replaceEmojis(text ?? '', preferredSkinTone, lang);
+    const {text: convertedText = '', emojis = [], cursorPosition} = replaceEmojis(text, preferredSkinTone, lang);
 
     return {
         text: convertedText,
diff --git a/src/pages/home/report/ParticipantLocalTime.tsx b/src/pages/home/report/ParticipantLocalTime.tsx
index 58d85c0662b2..f51032690a33 100644
--- a/src/pages/home/report/ParticipantLocalTime.tsx
+++ b/src/pages/home/report/ParticipantLocalTime.tsx
@@ -12,16 +12,13 @@ import type {PersonalDetails} from '@src/types/onyx';
 type ParticipantLocalTimeProps = {
     /** Personal details of the participant */
     participant: PersonalDetails;
-
-    /** The user's preferred locale e.g. 'en', 'es-ES' */
-    preferredLocale?: Locale;
 };
 
-function getParticipantLocalTime(participant: PersonalDetails, preferredLocale: Locale | undefined) {
+function getParticipantLocalTime(participant: PersonalDetails, preferredLocale: Locale) {
     // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Disabling this line for safeness as nullish coalescing works only if the value is undefined or null
     const reportRecipientTimezone = participant.timezone || CONST.DEFAULT_TIME_ZONE;
-    const reportTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale ?? CONST.LOCALES.DEFAULT, undefined, reportRecipientTimezone.selected);
-    const currentTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale ?? CONST.LOCALES.DEFAULT);
+    const reportTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale, undefined, reportRecipientTimezone.selected);
+    const currentTimezone = DateUtils.getLocalDateFromDatetime(preferredLocale);
     const reportRecipientDay = DateUtils.formatToDayOfWeek(reportTimezone);
     const currentUserDay = DateUtils.formatToDayOfWeek(currentTimezone);
     if (reportRecipientDay !== currentUserDay) {
@@ -30,8 +27,8 @@ function getParticipantLocalTime(participant: PersonalDetails, preferredLocale:
     return `${DateUtils.formatToLocalTime(reportTimezone)}`;
 }
 
-function ParticipantLocalTime({participant, preferredLocale}: ParticipantLocalTimeProps) {
-    const {translate} = useLocalize();
+function ParticipantLocalTime({participant}: ParticipantLocalTimeProps) {
+    const {translate, preferredLocale} = useLocalize();
     const styles = useThemeStyles();
 
     const [localTime, setLocalTime] = useState(() => getParticipantLocalTime(participant, preferredLocale));
diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
index e6a75e6f157b..1ae8e63bd0f1 100644
--- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
+++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
@@ -1,15 +1,14 @@
 import {useIsFocused} from '@react-navigation/native';
-import type {FC} from 'react';
 import React, {useCallback, useEffect, useMemo} from 'react';
 import {View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
-import type {SvgProps} from 'react-native-svg';
 import type {ValueOf} from 'type-fest';
 import type {FileObject} from '@components/AttachmentModal';
 import AttachmentPicker from '@components/AttachmentPicker';
 import Icon from '@components/Icon';
 import * as Expensicons from '@components/Icon/Expensicons';
+import type {PopoverMenuItem} from '@components/PopoverMenu';
 import PopoverMenu from '@components/PopoverMenu';
 import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
 import Tooltip from '@components/Tooltip/PopoverAnchorTooltip';
@@ -29,25 +28,19 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import type * as OnyxTypes from '@src/types/onyx';
 
-type MoneyRequestOption = {
-    icon: FC<SvgProps>;
-    text: string;
-    onSelected: () => void;
-};
-
-type MoneyRequestOptions = Record<ValueOf<typeof CONST.IOU.TYPE>, MoneyRequestOption>;
+type MoneyRequestOptions = Record<ValueOf<typeof CONST.IOU.TYPE>, PopoverMenuItem>;
 
 type AttachmentPickerWithMenuItemsOnyxProps = {
     /** The policy tied to the report */
     policy: OnyxEntry<OnyxTypes.Policy>;
 };
 
-type AttachmentPickerWithMenuItemsProps = {
+type AttachmentPickerWithMenuItemsProps = AttachmentPickerWithMenuItemsOnyxProps & {
     /** The report currently being looked at */
     report: OnyxEntry<OnyxTypes.Report>;
 
     /** Callback to open the file in the modal */
-    displayFileInModal: (url: FileObject | undefined) => void;
+    displayFileInModal: (url: FileObject) => void;
 
     /** Whether or not the full size composer is available */
     isFullComposerAvailable: boolean;
@@ -59,7 +52,7 @@ type AttachmentPickerWithMenuItemsProps = {
     isBlockedFromConcierge: boolean;
 
     /** Whether or not the attachment picker is disabled */
-    disabled: boolean;
+    disabled?: boolean;
 
     /** Sets the menu visibility */
     setMenuVisibility: (isVisible: boolean) => void;
@@ -93,7 +86,7 @@ type AttachmentPickerWithMenuItemsProps = {
 
     /** The personal details of everyone in the report */
     reportParticipantIDs?: number[];
-} & AttachmentPickerWithMenuItemsOnyxProps;
+};
 
 /**
  * This includes the popover of options you see when pressing the + button in the composer.
@@ -155,7 +148,7 @@ function AttachmentPickerWithMenuItems({
     /**
      * Determines if we can show the task option
      */
-    const taskOption: MoneyRequestOption[] = useMemo(() => {
+    const taskOption: PopoverMenuItem[] = useMemo(() => {
         if (!ReportUtils.canCreateTaskInReport(report)) {
             return [];
         }
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index b54ccaf20518..6b89a591055a 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -78,99 +78,99 @@ type ComposerWithSuggestionsOnyxProps = {
     editFocused: OnyxEntry<boolean>;
 };
 
-type ComposerWithSuggestionsProps = {
-    /** Report ID */
-    reportID: string;
+type ComposerWithSuggestionsProps = ComposerWithSuggestionsOnyxProps &
+    Partial<ChildrenProps> & {
+        /** Report ID */
+        reportID: string;
 
-    /** Callback to focus composer */
-    onFocus: () => void;
+        /** Callback to focus composer */
+        onFocus: () => void;
 
-    /** Callback to blur composer */
-    onBlur: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
+        /** Callback to blur composer */
+        onBlur: (event: NativeSyntheticEvent<TextInputFocusEventData>) => void;
 
-    /** Callback to update the value of the composer */
-    onValueChange: (value: string) => void;
+        /** Callback to update the value of the composer */
+        onValueChange: (value: string) => void;
 
-    /** Whether the composer is full size */
-    isComposerFullSize: boolean;
+        /** Whether the composer is full size */
+        isComposerFullSize: boolean;
 
-    /** Whether the menu is visible */
-    isMenuVisible: boolean;
+        /** Whether the menu is visible */
+        isMenuVisible: boolean;
 
-    /** The placeholder for the input */
-    inputPlaceholder: string;
+        /** The placeholder for the input */
+        inputPlaceholder: string;
 
-    /** Function to display a file in a modal */
-    displayFileInModal: (file: FileObject | undefined) => void;
+        /** Function to display a file in a modal */
+        displayFileInModal: (file: FileObject) => void;
 
-    /** Whether the text input should clear */
-    textInputShouldClear: boolean;
+        /** Whether the text input should clear */
+        textInputShouldClear: boolean;
 
-    /** Function to set the text input should clear */
-    setTextInputShouldClear: (shouldClear: boolean) => void;
+        /** Function to set the text input should clear */
+        setTextInputShouldClear: (shouldClear: boolean) => void;
 
-    /** Whether the user is blocked from concierge */
-    isBlockedFromConcierge: boolean;
+        /** Whether the user is blocked from concierge */
+        isBlockedFromConcierge: boolean;
 
-    /** Whether the input is disabled */
-    disabled: boolean;
+        /** Whether the input is disabled */
+        disabled: boolean;
 
-    /** Whether the full composer is available */
-    isFullComposerAvailable: boolean;
+        /** Whether the full composer is available */
+        isFullComposerAvailable: boolean;
 
-    /** Function to set whether the full composer is available */
-    setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void;
+        /** Function to set whether the full composer is available */
+        setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void;
 
-    /** Function to set whether the comment is empty */
-    setIsCommentEmpty: (isCommentEmpty: boolean) => void;
+        /** Function to set whether the comment is empty */
+        setIsCommentEmpty: (isCommentEmpty: boolean) => void;
 
-    /** Function to handle sending a message */
-    handleSendMessage: () => void;
+        /** Function to handle sending a message */
+        handleSendMessage: () => void;
 
-    /** Whether the compose input should show */
-    shouldShowComposeInput: OnyxEntry<boolean>;
+        /** Whether the compose input should show */
+        shouldShowComposeInput: OnyxEntry<boolean>;
 
-    /** Function to measure the parent container */
-    measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void;
+        /** Function to measure the parent container */
+        measureParentContainer: (callback: MeasureInWindowOnSuccessCallback) => void;
 
-    /** The height of the list */
-    listHeight: number;
+        /** The height of the list */
+        listHeight: number;
 
-    /** Whether the scroll is likely to trigger a layout */
-    isScrollLikelyLayoutTriggered: RefObject<boolean>;
+        /** Whether the scroll is likely to trigger a layout */
+        isScrollLikelyLayoutTriggered: RefObject<boolean>;
 
-    /** Function to raise the scroll is likely layout triggered */
-    raiseIsScrollLikelyLayoutTriggered: () => void;
+        /** Function to raise the scroll is likely layout triggered */
+        raiseIsScrollLikelyLayoutTriggered: () => void;
 
-    /** The ref to the suggestions */
-    suggestionsRef: React.RefObject<SuggestionsRef>;
+        /** The ref to the suggestions */
+        suggestionsRef: React.RefObject<SuggestionsRef>;
 
-    /** The ref to the animated input */
-    animatedRef: AnimatedRef;
+        /** The ref to the animated input */
+        animatedRef: AnimatedRef;
 
-    /** The ref to the next modal will open */
-    isNextModalWillOpenRef: MutableRefObject<boolean | null>;
+        /** The ref to the next modal will open */
+        isNextModalWillOpenRef: MutableRefObject<boolean | null>;
 
-    /** Whether the edit is focused */
-    editFocused: boolean;
+        /** Whether the edit is focused */
+        editFocused: boolean;
 
-    /** Wheater chat is empty */
-    isEmptyChat?: boolean;
+        /** Wheater chat is empty */
+        isEmptyChat?: boolean;
 
-    /** The last report action */
-    lastReportAction?: OnyxTypes.ReportAction;
+        /** The last report action */
+        lastReportAction?: OnyxTypes.ReportAction;
 
-    /** Whether to include chronos */
-    includeChronos?: boolean;
+        /** Whether to include chronos */
+        includeChronos?: boolean;
 
-    /** The parent report action ID */
-    parentReportActionID?: string;
+        /** The parent report action ID */
+        parentReportActionID?: string;
 
-    /** The parent report ID */
-    // eslint-disable-next-line react/no-unused-prop-types
-    parentReportID: string | undefined;
-} & ComposerWithSuggestionsOnyxProps &
-    Partial<ChildrenProps>;
+        /** The parent report ID */
+        // eslint-disable-next-line react/no-unused-prop-types -- its used in the withOnyx HOC
+        parentReportID: string | undefined;
+    };
 
 const {RNTextInputReset} = NativeModules;
 
@@ -240,7 +240,7 @@ function ComposerWithSuggestions(
         // For testing
         children,
     }: ComposerWithSuggestionsProps,
-    ref: ForwardedRef<ComposerRef | null>,
+    ref: ForwardedRef<ComposerRef>,
 ) {
     const {isKeyboardShown} = useKeyboardState();
     const theme = useTheme();
@@ -340,24 +340,24 @@ function ComposerWithSuggestions(
             let currentIndex = 0;
 
             // Find the first character mismatch with newText
-            while (currentIndex < newText.length && prevText.charAt(currentIndex) === newText?.charAt(currentIndex) && selection.start > currentIndex) {
+            while (currentIndex < newText.length && prevText.charAt(currentIndex) === newText.charAt(currentIndex) && selection.start > currentIndex) {
                 currentIndex++;
             }
 
-            if (currentIndex < (newText?.length ?? 0)) {
+            if (currentIndex < newText.length) {
                 startIndex = currentIndex;
-                const commonSuffixLength = ComposerUtils.findCommonSuffixLength(prevText, newText ?? '', selection.end);
+                const commonSuffixLength = ComposerUtils.findCommonSuffixLength(prevText, newText, selection.end);
                 // if text is getting pasted over find length of common suffix and subtract it from new text length
                 if (commonSuffixLength > 0 || selection.end - selection.start > 0) {
-                    endIndex = (newText?.length ?? 0) - commonSuffixLength;
+                    endIndex = newText.length - commonSuffixLength;
                 } else {
-                    endIndex = currentIndex + (newText?.length ?? 0);
+                    endIndex = currentIndex + newText.length;
                 }
             }
             return {
                 startIndex,
                 endIndex,
-                diff: newText?.substring(startIndex, endIndex) ?? '',
+                diff: newText.substring(startIndex, endIndex),
             };
         },
         [selection.start, selection.end],
@@ -375,7 +375,7 @@ function ComposerWithSuggestions(
                 text: newComment,
                 emojis,
                 cursorPosition,
-            } = EmojiUtils.replaceAndExtractEmojis(isEmojiInserted ? ComposerUtils.insertWhiteSpaceAtIndex(commentValue, endIndex) : commentValue ?? '', preferredSkinTone, preferredLocale);
+            } = EmojiUtils.replaceAndExtractEmojis(isEmojiInserted ? ComposerUtils.insertWhiteSpaceAtIndex(commentValue, endIndex) : commentValue, preferredSkinTone, preferredLocale);
             if (emojis.length) {
                 const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current);
                 if (newEmojis.length) {
@@ -748,7 +748,7 @@ function ComposerWithSuggestions(
                     isComposerFullSize={isComposerFullSize}
                     value={value}
                     testID="composer"
-                    numberOfLines={numberOfLines ?? 0}
+                    numberOfLines={numberOfLines ?? undefined}
                     onNumberOfLinesChange={updateNumberOfLines}
                     shouldCalculateCaretPosition
                     onLayout={onLayout}
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
index 0fa535329d33..7f169ef15918 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e.tsx
@@ -16,7 +16,7 @@ function IncrementRenderCount() {
     return null;
 }
 
-function ComposerWithSuggestionsE2e(props: ComposerWithSuggestionsProps, ref: ForwardedRef<ComposerRef | null>) {
+function ComposerWithSuggestionsE2e(props: ComposerWithSuggestionsProps, ref: ForwardedRef<ComposerRef>) {
     // Eventually Auto focus on e2e tests
     useEffect(() => {
         const testConfig = E2EClient.getCurrentActiveTestConfig();
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 98630ff88b23..283f0dfab18f 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -289,9 +289,7 @@ function ReportActionCompose({
      */
     const submitForm = useCallback(
         (event?: SyntheticEvent) => {
-            if (event) {
-                event.preventDefault();
-            }
+            event?.preventDefault();
 
             const newComment = composerRef.current?.prepareCommentAndResetComposer();
             if (!newComment) {
diff --git a/src/pages/home/report/ReportActionCompose/SendButton.tsx b/src/pages/home/report/ReportActionCompose/SendButton.tsx
index 4726c1638f42..c505eb0e32e7 100644
--- a/src/pages/home/report/ReportActionCompose/SendButton.tsx
+++ b/src/pages/home/report/ReportActionCompose/SendButton.tsx
@@ -23,12 +23,9 @@ function SendButton({isDisabled: isDisabledProp, handleSendMessage}: SendButtonP
     const styles = useThemeStyles();
     const {translate} = useLocalize();
 
-    const Tap = Gesture.Tap()
-        // @ts-expect-error Enabled require argument but when passing something button is not working
-        .enabled()
-        .onEnd(() => {
-            handleSendMessage();
-        });
+    const Tap = Gesture.Tap().onEnd(() => {
+        handleSendMessage();
+    });
 
     return (
         <View
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx
index e33080d971f2..a3eafd3bf128 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.android.tsx
@@ -13,7 +13,7 @@ import type {SilentCommentUpdaterOnyxProps, SilentCommentUpdaterProps} from './t
  * It is connected to the actual draft comment in onyx. The comment in onyx might updates multiple times, and we want to avoid
  * re-rendering a UI component for that. That's why the side effect was moved down to a separate component.
  */
-function SilentCommentUpdater({comment = '', updateComment}: SilentCommentUpdaterProps) {
+function SilentCommentUpdater({comment, updateComment}: SilentCommentUpdaterProps) {
     useEffect(() => {
         updateComment(comment ?? '');
         // eslint-disable-next-line react-hooks/exhaustive-deps -- We need to run this on mount
diff --git a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
index 206c91ed9b08..0ae45d2d705d 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
+++ b/src/pages/home/report/ReportActionCompose/SuggestionEmoji.tsx
@@ -28,7 +28,7 @@ type SuggestionEmojiOnyxProps = {
 type SuggestionEmojiProps = SuggestionProps &
     SuggestionEmojiOnyxProps & {
         /** Function to clear the input */
-        resetKeyboardInput: (() => void) | undefined;
+        resetKeyboardInput?: () => void;
     };
 
 /**
diff --git a/src/pages/home/report/ReportTypingIndicator.tsx b/src/pages/home/report/ReportTypingIndicator.tsx
index e484ce9886a7..3ff8f2b0eb8e 100755
--- a/src/pages/home/report/ReportTypingIndicator.tsx
+++ b/src/pages/home/report/ReportTypingIndicator.tsx
@@ -41,7 +41,7 @@ function ReportTypingIndicator({userTypingStatuses}: ReportTypingIndicatorProps)
     if (usersTyping.length === 1) {
         return (
             <TextWithEllipsis
-                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case
                 leadingText={firstUserTypingDisplayName || translate('common.someone')}
                 trailingText={` ${translate('reportTypingIndicator.isTyping')}`}
                 textStyle={[styles.chatItemComposeSecondaryRowSubText]}

From 022db8f5be1264e9fb45a7393c84ab2d309a97af Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Wed, 14 Feb 2024 16:45:57 +0100
Subject: [PATCH 25/29] fix: resolve comments

---
 src/components/AttachmentModal.tsx            |  4 ++--
 .../AddCommentOrAttachementParams.ts          |  2 +-
 src/libs/ReportUtils.ts                       |  2 +-
 src/libs/actions/Report.ts                    |  4 ++--
 .../ComposerWithSuggestions.tsx               | 10 ++++----
 .../ReportActionCompose.tsx                   | 24 ++++---------------
 .../SilentCommentUpdater/index.tsx            |  1 +
 src/types/modules/pusher.d.ts                 |  2 +-
 src/types/onyx/ReportAction.ts                |  2 +-
 9 files changed, 19 insertions(+), 32 deletions(-)

diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx
index 2b4afc037796..7f0178863fc9 100755
--- a/src/components/AttachmentModal.tsx
+++ b/src/components/AttachmentModal.tsx
@@ -89,7 +89,7 @@ type AttachmentModalProps = AttachmentModalOnyxProps & {
     source?: AvatarSource;
 
     /** Optional callback to fire when we want to preview an image and approve it for use. */
-    onConfirm?: ((file: Partial<FileObject>) => void) | null;
+    onConfirm?: ((file: FileObject) => void) | null;
 
     /** Whether the modal should be open by default */
     defaultOpen?: boolean;
@@ -264,7 +264,7 @@ function AttachmentModal({
         }
 
         if (onConfirm) {
-            onConfirm(Object.assign(file ?? {}, {source: sourceState}));
+            onConfirm(Object.assign(file ?? {}, {source: sourceState} as FileObject));
         }
 
         setIsModalOpen(false);
diff --git a/src/libs/API/parameters/AddCommentOrAttachementParams.ts b/src/libs/API/parameters/AddCommentOrAttachementParams.ts
index 4eab35be7dd2..a705c92f7f27 100644
--- a/src/libs/API/parameters/AddCommentOrAttachementParams.ts
+++ b/src/libs/API/parameters/AddCommentOrAttachementParams.ts
@@ -5,7 +5,7 @@ type AddCommentOrAttachementParams = {
     reportActionID?: string;
     commentReportActionID?: string | null;
     reportComment?: string;
-    file?: Partial<FileObject>;
+    file?: FileObject;
     timezone?: string;
     shouldAllowActionableMentionWhispers?: boolean;
     clientCreatedTime?: string;
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 2b073b110184..ed3dc16b56a4 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -2631,7 +2631,7 @@ function getReportDescriptionText(report: Report): string {
     return parser.htmlToText(report.description);
 }
 
-function buildOptimisticAddCommentReportAction(text?: string, file?: Partial<FileObject>, actorAccountID?: number): OptimisticReportAction {
+function buildOptimisticAddCommentReportAction(text?: string, file?: FileObject, actorAccountID?: number): OptimisticReportAction {
     const parser = new ExpensiMark();
     const commentText = getParsedComment(text ?? '');
     const isAttachment = !text && file !== undefined;
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index bfae6ece1fa1..7127178daae7 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -353,7 +353,7 @@ function notifyNewAction(reportID: string, accountID?: number, reportActionID?:
  * - Adding one attachment
  * - Add both a comment and attachment simultaneously
  */
-function addActions(reportID: string, text = '', file?: Partial<FileObject>) {
+function addActions(reportID: string, text = '', file?: FileObject) {
     let reportCommentText = '';
     let reportCommentAction: OptimisticAddCommentReportAction | undefined;
     let attachmentAction: OptimisticAddCommentReportAction | undefined;
@@ -512,7 +512,7 @@ function addActions(reportID: string, text = '', file?: Partial<FileObject>) {
 }
 
 /** Add an attachment and optional comment. */
-function addAttachment(reportID: string, file: Partial<FileObject>, text = '') {
+function addAttachment(reportID: string, file: FileObject, text = '') {
     addActions(reportID, text, file);
 }
 
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index cfe45e5a011b..a345926ad7d2 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -214,6 +214,7 @@ function ComposerWithSuggestions(
         onFocus,
         onBlur,
         onValueChange,
+
         // Composer
         isComposerFullSize,
         isMenuVisible,
@@ -232,11 +233,13 @@ function ComposerWithSuggestions(
         listHeight,
         isScrollLikelyLayoutTriggered,
         raiseIsScrollLikelyLayoutTriggered,
+
         // Refs
         suggestionsRef,
         animatedRef,
         isNextModalWillOpenRef,
         editFocused,
+
         // For testing
         children,
     }: ComposerWithSuggestionsProps,
@@ -278,7 +281,7 @@ function ComposerWithSuggestions(
 
     const syncSelectionWithOnChangeTextRef = useRef<SyncSelection | null>(null);
 
-    const suggestions = suggestionsRef.current?.getSuggestions() ?? (() => []);
+    const suggestions = suggestionsRef.current?.getSuggestions() ?? [];
 
     const hasEnoughSpaceForLargeSuggestion = SuggestionUtils.hasEnoughSpaceForLargeSuggestionMenu(listHeight, composerHeight, suggestions?.length ?? 0);
 
@@ -457,7 +460,7 @@ function ComposerWithSuggestions(
         [reportID, numberOfLines],
     );
 
-    const prepareCommentAndResetComposer = useCallback(() => {
+    const prepareCommentAndResetComposer = useCallback((): string => {
         const trimmedComment = commentRef.current.trim();
         const commentLength = ReportUtils.getCommentLength(trimmedComment);
 
@@ -575,7 +578,6 @@ function ComposerWithSuggestions(
     /**
      * Focus the composer text input
      * @param [shouldDelay=false] Impose delay before focusing the composer
-     * @memberof ReportActionCompose
      */
     const focus = useCallback((shouldDelay = false) => {
         focusComposerWithDelay(textInputRef.current)(shouldDelay);
@@ -614,7 +616,7 @@ function ComposerWithSuggestions(
             }
 
             // if we're typing on another input/text area, do not focus
-            if (['INPUT', 'TEXTAREA'].includes((e.target as Element)?.nodeName)) {
+            if (['INPUT', 'TEXTAREA'].includes((e.target as Element | null)?.nodeName ?? '')) {
                 return;
             }
 
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 735fb323d638..84f7600f8ef0 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -43,6 +43,7 @@ import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import AttachmentPickerWithMenuItems from './AttachmentPickerWithMenuItems';
 import ComposerWithSuggestions from './ComposerWithSuggestions';
+import type {ComposerWithSuggestionsProps} from './ComposerWithSuggestions/ComposerWithSuggestions';
 import SendButton from './SendButton';
 
 type ComposerRef = {
@@ -71,36 +72,19 @@ type ReportActionComposeOnyxProps = {
 };
 
 type ReportActionComposeProps = ReportActionComposeOnyxProps &
-    WithCurrentUserPersonalDetailsProps & {
+    WithCurrentUserPersonalDetailsProps &
+    Pick<ComposerWithSuggestionsProps, 'reportID' | 'isEmptyChat' | 'isComposerFullSize' | 'disabled' | 'listHeight' | 'lastReportAction'> & {
         /** A method to call when the form is submitted */
         onSubmit: (newComment: string | undefined) => void;
 
-        /** The ID of the report actions will be created for */
-        reportID: string;
-
         /** The report currently being looked at */
         report: OnyxEntry<OnyxTypes.Report>;
 
-        /** Is composer full size */
-        isComposerFullSize?: boolean;
-
-        /** Whether user interactions should be disabled */
-        disabled?: boolean;
-
-        /** Height of the list which the composer is part of */
-        listHeight?: number;
-
         /** The type of action that's pending  */
         pendingAction?: OnyxCommon.PendingAction;
 
         /** Whether the report is ready for display */
         isReportReadyForDisplay?: boolean;
-
-        /** Whether the chat is empty */
-        isEmptyChat?: boolean;
-
-        /** The last report action */
-        lastReportAction?: OnyxTypes.ReportAction;
     };
 
 // We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will
@@ -268,7 +252,7 @@ function ReportActionCompose({
     }, []);
 
     const addAttachment = useCallback(
-        (file: Partial<FileObject>) => {
+        (file: FileObject) => {
             playSound(SOUNDS.DONE);
             const newComment = composerRef?.current?.prepareCommentAndResetComposer();
             Report.addAttachment(reportID, file, newComment);
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
index c84bd3786610..1abc6567bc7b 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
@@ -43,5 +43,6 @@ SilentCommentUpdater.displayName = 'SilentCommentUpdater';
 export default withOnyx<SilentCommentUpdaterProps, SilentCommentUpdaterOnyxProps>({
     comment: {
         key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`,
+        initialValue: '',
     },
 })(SilentCommentUpdater);
diff --git a/src/types/modules/pusher.d.ts b/src/types/modules/pusher.d.ts
index 676d7a7ee2fc..e9aa50085e8d 100644
--- a/src/types/modules/pusher.d.ts
+++ b/src/types/modules/pusher.d.ts
@@ -9,6 +9,6 @@ declare global {
     // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
     interface File {
         source: string;
-        uri: string;
+        uri?: string;
     }
 }
diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts
index 7071c211c2e9..d339d666a411 100644
--- a/src/types/onyx/ReportAction.ts
+++ b/src/types/onyx/ReportAction.ts
@@ -169,7 +169,7 @@ type ReportActionBase = {
     isFirstItem?: boolean;
 
     /** Informations about attachments of report action */
-    attachmentInfo?: Partial<FileObject> | EmptyObject;
+    attachmentInfo?: FileObject | EmptyObject;
 
     /** Receipt tied to report action */
     receipt?: Receipt;

From 2a71a34beacca8b099be1161eceb072b6aa821f0 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Wed, 21 Feb 2024 14:22:53 +0100
Subject: [PATCH 26/29] fix: typecheck

---
 .../ReportActionCompose/AttachmentPickerWithMenuItems.tsx   | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
index 9888c5217e69..68c7f0883683 100644
--- a/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
+++ b/src/pages/home/report/ReportActionCompose/AttachmentPickerWithMenuItems.tsx
@@ -18,14 +18,12 @@ import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWindowDimensions from '@hooks/useWindowDimensions';
 import * as Browser from '@libs/Browser';
-import Navigation from '@libs/Navigation/Navigation';
 import * as ReportUtils from '@libs/ReportUtils';
 import * as IOU from '@userActions/IOU';
 import * as Report from '@userActions/Report';
 import * as Task from '@userActions/Task';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
 import type * as OnyxTypes from '@src/types/onyx';
 
 type MoneyRequestOptions = Record<ValueOf<typeof CONST.IOU.TYPE>, PopoverMenuItem>;
@@ -126,12 +124,12 @@ function AttachmentPickerWithMenuItems({
             [CONST.IOU.TYPE.SPLIT]: {
                 icon: Expensicons.Receipt,
                 text: translate('iou.splitBill'),
-                onSelected: () => IOU.startMoneyRequest_temporaryForRefactor(CONST.IOU.TYPE.SPLIT, report.reportID),
+                onSelected: () => IOU.startMoneyRequest_temporaryForRefactor(CONST.IOU.TYPE.SPLIT, report?.reportID ?? ''),
             },
             [CONST.IOU.TYPE.REQUEST]: {
                 icon: Expensicons.MoneyCircle,
                 text: translate('iou.requestMoney'),
-                onSelected: () => IOU.startMoneyRequest_temporaryForRefactor(CONST.IOU.TYPE.REQUEST, report.reportID),
+                onSelected: () => IOU.startMoneyRequest_temporaryForRefactor(CONST.IOU.TYPE.REQUEST, report?.reportID ?? ''),
             },
             [CONST.IOU.TYPE.SEND]: {
                 icon: Expensicons.Send,

From 2e829a169702d72ec6a526ed432b917576baa810 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Fri, 23 Feb 2024 11:11:33 +0100
Subject: [PATCH 27/29] fix: typecheck

---
 src/pages/home/report/ReportActionCompose/SuggestionMention.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
index f284596040b7..54d21d637d09 100644
--- a/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
+++ b/src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
@@ -152,7 +152,7 @@ function SuggestionMention(
             sortedPersonalDetails.slice(0, CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS - suggestions.length).forEach((detail) => {
                 suggestions.push({
                     text: PersonalDetailsUtils.getDisplayNameOrDefault(detail),
-                    alternateText: formatPhoneNumber(detail?.login),
+                    alternateText: formatPhoneNumber(detail?.login ?? ''),
                     login: detail?.login,
                     icons: [
                         {

From 1e19123cf64f548463fc0ddc8d48baab4db3bc50 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Mon, 26 Feb 2024 11:30:48 +0100
Subject: [PATCH 28/29] fix: typecheck

---
 src/components/AvatarWithImagePicker.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/AvatarWithImagePicker.tsx b/src/components/AvatarWithImagePicker.tsx
index fa8a6d71516f..4388ebb8f815 100644
--- a/src/components/AvatarWithImagePicker.tsx
+++ b/src/components/AvatarWithImagePicker.tsx
@@ -220,7 +220,7 @@ function AvatarWithImagePicker({
             setError(null, {});
             setIsMenuVisible(false);
             setImageData({
-                uri: image.uri,
+                uri: image.uri ?? '',
                 name: image.name,
                 type: image.type,
             });

From 64368d5784c3ad869251eb6845070fbfb5ef8f23 Mon Sep 17 00:00:00 2001
From: Jakub Butkiewicz <jakub.butkiewicz94@gmail.com>
Date: Wed, 28 Feb 2024 10:49:28 +0100
Subject: [PATCH 29/29] fix: typecheck

---
 src/libs/EmojiUtils.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts
index 353ced9e1e45..cab0f48d75fd 100644
--- a/src/libs/EmojiUtils.ts
+++ b/src/libs/EmojiUtils.ts
@@ -10,7 +10,6 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import type {FrequentlyUsedEmoji, Locale} from '@src/types/onyx';
 import type {ReportActionReaction, UsersReactions} from '@src/types/onyx/ReportActionReactions';
 import type IconAsset from '@src/types/utils/IconAsset';
-import {SupportedLanguage} from './EmojiTrie';
 
 type HeaderIndice = {code: string; index: number; icon: IconAsset};
 type EmojiSpacer = {code: string; spacer: boolean};
@@ -384,7 +383,7 @@ function replaceAndExtractEmojis(text: string, preferredSkinTone: number = CONST
  * Suggest emojis when typing emojis prefix after colon
  * @param [limit] - matching emojis limit
  */
-function suggestEmojis(text: string, lang: SupportedLanguage, limit: number = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined {
+function suggestEmojis(text: string, lang: Locale, limit: number = CONST.AUTO_COMPLETE_SUGGESTER.MAX_AMOUNT_OF_SUGGESTIONS): Emoji[] | undefined {
     // emojisTrie is importing the emoji JSON file on the app starting and we want to avoid it
     const emojisTrie = require('./EmojiTrie').default;
 
