diff --git a/contributingGuides/TS_STYLE.md b/contributingGuides/TS_STYLE.md index 72d2bdeefba2..d407019cbed6 100644 --- a/contributingGuides/TS_STYLE.md +++ b/contributingGuides/TS_STYLE.md @@ -27,6 +27,7 @@ - [1.20 Hooks instead of HOCs](#hooks-instead-of-hocs) - [1.21 `compose` usage](#compose-usage) - [1.22 Type imports](#type-imports) + - [1.23 Ref types](#ref-types) - [Exception to Rules](#exception-to-rules) - [Communication Items](#communication-items) - [Migration Guidelines](#migration-guidelines) @@ -640,6 +641,28 @@ type Foo = { export someVariable ``` +- [1.23](#ref-types) **Ref types**: Avoid using HTML elements while declaring refs. Please use React Native components where possible. React Native Web handles the references on its own. It also extends React Native components with [Interaction API](https://necolas.github.io/react-native-web/docs/interactions/) which should be used to handle Pointer and Mouse events. Exception of this rule is when we explicitly need to use functions available only in DOM and not in React Native, e.g. `getBoundingClientRect`. Then please declare ref type as `union` of React Native component and HTML element. When passing it to React Native component assert it as soon as possible using utility methods declared in `src/types/utils`. + +Normal usage: +```tsx +const ref = useRef(); + + {#DO SOMETHING}}> +``` + +Exceptional usage where DOM methods are necessary: +```tsx +import viewRef from '@src/types/utils/viewRef'; + +const ref = useRef(); + +if (ref.current && 'getBoundingClientRect' in ref.current) { + ref.current.getBoundingClientRect(); +} + + {#DO SOMETHING}}> +``` + ## Exception to Rules Most of the rules are enforced in ESLint or checked by TypeScript. If you think your particular situation warrants an exception, post the context in the `#expensify-open-source` Slack channel with your message prefixed with `TS EXCEPTION:`. The internal engineer assigned to the PR should be the one that approves each exception, however all discussion regarding granting exceptions should happen in the public channel instead of the GitHub PR page so that the TS migration team can access them easily. diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index ccd0f21626a0..32ac018acb04 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -1,7 +1,6 @@ import {FlashList} from '@shopify/flash-list'; -import type {ForwardedRef, ReactElement} from 'react'; -import React, {forwardRef, useCallback, useEffect, useMemo, useRef} from 'react'; -import type {View} from 'react-native'; +import type {ReactElement} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; // We take ScrollView from this package to properly handle the scrolling of AutoCompleteSuggestions in chats since one scroll is nested inside another import {ScrollView} from 'react-native-gesture-handler'; import Animated, {Easing, FadeOutDown, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; @@ -10,9 +9,9 @@ import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import viewForwardedRef from '@src/types/utils/viewForwardedRef'; import type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps} from './types'; const measureHeightOfSuggestionRows = (numRows: number, isSuggestionPickerLarge: boolean): number => { @@ -30,18 +29,22 @@ const measureHeightOfSuggestionRows = (numRows: number, isSuggestionPickerLarge: return numRows * CONST.AUTO_COMPLETE_SUGGESTER.SUGGESTION_ROW_HEIGHT; }; -function BaseAutoCompleteSuggestions( - { - highlightedSuggestionIndex, - onSelect, - accessibilityLabelExtractor, - renderSuggestionMenuItem, - suggestions, - isSuggestionPickerLarge, - keyExtractor, - }: AutoCompleteSuggestionsProps, - ref: ForwardedRef, -) { +/** + * On the mobile-web platform, when long-pressing on auto-complete suggestions, + * we need to prevent focus shifting to avoid blurring the main input (which makes the suggestions picker close and fires the onSelect callback). + * The desired pattern for all platforms is to do nothing on long-press. + * On the native platform, tapping on auto-complete suggestions will not blur the main input. + */ + +function BaseAutoCompleteSuggestions({ + highlightedSuggestionIndex, + onSelect, + accessibilityLabelExtractor, + renderSuggestionMenuItem, + suggestions, + isSuggestionPickerLarge, + keyExtractor, +}: AutoCompleteSuggestionsProps) { const {windowWidth, isLargeScreenWidth} = useWindowDimensions(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -92,9 +95,14 @@ function BaseAutoCompleteSuggestions( return ( { + if (DeviceCapabilities.hasHoverSupport()) { + return; + } + e.preventDefault(); + }} > ( BaseAutoCompleteSuggestions.displayName = 'BaseAutoCompleteSuggestions'; -export default forwardRef(BaseAutoCompleteSuggestions); +export default BaseAutoCompleteSuggestions; diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index baca4011a177..c7f2aaea4d82 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -3,39 +3,17 @@ import ReactDOM from 'react-dom'; import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; -/** - * On the mobile-web platform, when long-pressing on auto-complete suggestions, - * we need to prevent focus shifting to avoid blurring the main input (which makes the suggestions picker close and fires the onSelect callback). - * The desired pattern for all platforms is to do nothing on long-press. - * On the native platform, tapping on auto-complete suggestions will not blur the main input. - */ - function AutoCompleteSuggestions({measureParentContainer = () => {}, ...props}: AutoCompleteSuggestionsProps) { const StyleUtils = useStyleUtils(); - const containerRef = React.useRef(null); const {windowHeight, windowWidth} = useWindowDimensions(); const [{width, left, bottom}, setContainerState] = React.useState({ width: 0, left: 0, bottom: 0, }); - React.useEffect(() => { - const container = containerRef.current; - if (!container) { - return () => {}; - } - container.onpointerdown = (e) => { - if (DeviceCapabilities.hasHoverSupport()) { - return; - } - e.preventDefault(); - }; - return () => (container.onpointerdown = null); - }, []); React.useEffect(() => { if (!measureParentContainer) { @@ -48,7 +26,6 @@ function AutoCompleteSuggestions({measureParentContainer = () => {} // eslint-disable-next-line react/jsx-props-no-spreading {...props} - ref={containerRef} /> ); diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index b6e20164348b..6b65fb56ea24 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -204,7 +204,7 @@ function Button( isSplitButton = false, ...rest }: ButtonProps, - ref: ForwardedRef, + ref: ForwardedRef, ) { const theme = useTheme(); const styles = useThemeStyles(); diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx index a28b7ebf0864..094c26a2b387 100644 --- a/src/components/ButtonWithDropdownMenu/index.tsx +++ b/src/components/ButtonWithDropdownMenu/index.tsx @@ -41,7 +41,7 @@ function ButtonWithDropdownMenu({ const [isMenuVisible, setIsMenuVisible] = useState(false); const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); const {windowWidth, windowHeight} = useWindowDimensions(); - const caretButton = useRef(null); + const caretButton = useRef(null); const selectedItem = options[selectedItemIndex] || options[0]; const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(buttonSize); const isButtonSizeLarge = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE; diff --git a/src/components/DragAndDrop/NoDropZone/index.tsx b/src/components/DragAndDrop/NoDropZone/index.tsx index 4760a16fd20b..3438bfff7c05 100644 --- a/src/components/DragAndDrop/NoDropZone/index.tsx +++ b/src/components/DragAndDrop/NoDropZone/index.tsx @@ -2,20 +2,22 @@ import React, {useRef} from 'react'; import {View} from 'react-native'; import useDragAndDrop from '@hooks/useDragAndDrop'; import useThemeStyles from '@hooks/useThemeStyles'; +import htmlDivElementRef from '@src/types/utils/htmlDivElementRef'; +import viewRef from '@src/types/utils/viewRef'; import type NoDropZoneProps from './types'; function NoDropZone({children}: NoDropZoneProps) { const styles = useThemeStyles(); - const noDropZone = useRef(null); + const noDropZone = useRef(null); useDragAndDrop({ - dropZone: noDropZone, + dropZone: htmlDivElementRef(noDropZone), shouldAllowDrop: false, }); return ( {children} diff --git a/src/components/DragAndDrop/Provider/index.tsx b/src/components/DragAndDrop/Provider/index.tsx index a5da9cc45a36..dc02eea2b12c 100644 --- a/src/components/DragAndDrop/Provider/index.tsx +++ b/src/components/DragAndDrop/Provider/index.tsx @@ -4,6 +4,8 @@ import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; import useDragAndDrop from '@hooks/useDragAndDrop'; import useThemeStyles from '@hooks/useThemeStyles'; +import htmlDivElementRef from '@src/types/utils/htmlDivElementRef'; +import viewRef from '@src/types/utils/viewRef'; import type {DragAndDropContextParams, DragAndDropProviderProps, SetOnDropHandlerCallback} from './types'; const DragAndDropContext = React.createContext({}); @@ -14,7 +16,7 @@ function shouldAcceptDrop(event: DragEvent): boolean { function DragAndDropProvider({children, isDisabled = false, setIsDraggingOver = () => {}}: DragAndDropProviderProps) { const styles = useThemeStyles(); - const dropZone = useRef(null); + const dropZone = useRef(null); const dropZoneID = useRef(Str.guid('drag-n-drop')); const onDropHandler = useRef(() => {}); @@ -23,7 +25,7 @@ function DragAndDropProvider({children, isDisabled = false, setIsDraggingOver = }, []); const {isDraggingOver} = useDragAndDrop({ - dropZone, + dropZone: htmlDivElementRef(dropZone), onDrop: onDropHandler.current, shouldAcceptDrop, isDisabled, @@ -38,7 +40,7 @@ function DragAndDropProvider({children, isDisabled = false, setIsDraggingOver = return ( {isDraggingOver && ( diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx index 1336654cdf2f..0a7adabe009f 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.tsx @@ -1,4 +1,5 @@ import React, {useEffect, useRef} from 'react'; +import type {View} from 'react-native'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -18,7 +19,7 @@ function EmojiPickerMenuItem({ isHighlighted = false, isUsingKeyboardMovement = false, }: EmojiPickerMenuItemProps) { - const ref = useRef(null); + const ref = useRef(null); const StyleUtils = useStyleUtils(); const themeStyles = useThemeStyles(); diff --git a/src/components/FocusableMenuItem.tsx b/src/components/FocusableMenuItem.tsx index e3ec8394dfa0..7868e6e11b0e 100644 --- a/src/components/FocusableMenuItem.tsx +++ b/src/components/FocusableMenuItem.tsx @@ -5,7 +5,7 @@ import type {MenuItemProps} from './MenuItem'; import MenuItem from './MenuItem'; function FocusableMenuItem(props: MenuItemProps) { - const ref = useRef(null); + const ref = useRef(null); // Sync focus on an item useSyncFocus(ref, Boolean(props.focused)); diff --git a/src/components/KYCWall/BaseKYCWall.tsx b/src/components/KYCWall/BaseKYCWall.tsx index 7d2f99f49593..d37e00727fa6 100644 --- a/src/components/KYCWall/BaseKYCWall.tsx +++ b/src/components/KYCWall/BaseKYCWall.tsx @@ -69,7 +69,7 @@ function KYCWall({ walletTerms, shouldShowPersonalBankAccountOption = false, }: BaseKYCWallProps) { - const anchorRef = useRef(null); + const anchorRef = useRef(null); const transferBalanceButtonRef = useRef(null); const [shouldShowAddPaymentMenu, setShouldShowAddPaymentMenu] = useState(false); diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index 2a77d101f6b3..ccd1cb883246 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -37,7 +37,7 @@ function BaseListItem({ const styles = useThemeStyles(); const {hovered, bind} = useHover(); - const pressableRef = useRef(null); + const pressableRef = useRef(null); // Sync focus on an item useSyncFocus(pressableRef, Boolean(isFocused), shouldSyncFocus); diff --git a/src/components/TextInput/TextInputLabel/index.tsx b/src/components/TextInput/TextInputLabel/index.tsx index 8f6d3efdcd8d..e1083b88414b 100644 --- a/src/components/TextInput/TextInputLabel/index.tsx +++ b/src/components/TextInput/TextInputLabel/index.tsx @@ -4,14 +4,15 @@ import type {Text} from 'react-native'; import {Animated} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; +import textRef from '@src/types/utils/textRef'; import type TextInputLabelProps from './types'; function TextInputLabel({for: inputId = '', label, labelTranslateY, labelScale}: TextInputLabelProps) { const styles = useThemeStyles(); - const labelRef = useRef(null); + const labelRef = useRef(null); useEffect(() => { - if (!inputId || !labelRef.current) { + if (!inputId || !labelRef.current || !('setAttribute' in labelRef.current)) { return; } labelRef.current.setAttribute('for', inputId); @@ -20,7 +21,7 @@ function TextInputLabel({for: inputId = '', label, labelTranslateY, labelScale}: return ( diff --git a/src/components/ThreeDotsMenu/index.tsx b/src/components/ThreeDotsMenu/index.tsx index f6b1f444a24b..72f7f31ded9d 100644 --- a/src/components/ThreeDotsMenu/index.tsx +++ b/src/components/ThreeDotsMenu/index.tsx @@ -41,7 +41,7 @@ function ThreeDotsMenu({ const theme = useTheme(); const styles = useThemeStyles(); const [isPopupMenuVisible, setPopupMenuVisible] = useState(false); - const buttonRef = useRef(null); + const buttonRef = useRef(null); const {translate} = useLocalize(); const isBehindModal = modal?.willAlertModalBecomeVisible && !modal?.isPopover && !shouldOverlay; diff --git a/src/components/WorkspaceSwitcherButton.tsx b/src/components/WorkspaceSwitcherButton.tsx index 94577eecf5b2..7f7715f6f827 100644 --- a/src/components/WorkspaceSwitcherButton.tsx +++ b/src/components/WorkspaceSwitcherButton.tsx @@ -24,7 +24,7 @@ function WorkspaceSwitcherButton({policy}: WorkspaceSwitcherButtonProps) { const {translate} = useLocalize(); const theme = useTheme(); - const pressableRef = useRef(null); + const pressableRef = useRef(null); const {source, name, type, id} = useMemo(() => { if (!policy) { diff --git a/src/hooks/useDragAndDrop/index.native.ts b/src/hooks/useDragAndDrop/index.native.ts new file mode 100644 index 000000000000..6bdc85bf0723 --- /dev/null +++ b/src/hooks/useDragAndDrop/index.native.ts @@ -0,0 +1,5 @@ +import type UseDragAndDrop from './types'; + +const useDragAndDrop: UseDragAndDrop = () => ({isDraggingOver: false}); + +export default useDragAndDrop; diff --git a/src/hooks/useDragAndDrop.ts b/src/hooks/useDragAndDrop/index.ts similarity index 87% rename from src/hooks/useDragAndDrop.ts rename to src/hooks/useDragAndDrop/index.ts index 7644d7bba5f0..b278039ee020 100644 --- a/src/hooks/useDragAndDrop.ts +++ b/src/hooks/useDragAndDrop/index.ts @@ -1,8 +1,7 @@ import {useIsFocused} from '@react-navigation/native'; -import type React from 'react'; import {useCallback, useContext, useEffect, useRef, useState} from 'react'; -import type {View} from 'react-native'; import {PopoverContext} from '@components/PopoverProvider'; +import type UseDragAndDrop from './types'; const COPY_DROP_EFFECT = 'copy'; const NONE_DROP_EFFECT = 'none'; @@ -11,22 +10,10 @@ const DRAG_OVER_EVENT = 'dragover'; const DRAG_LEAVE_EVENT = 'dragleave'; const DROP_EVENT = 'drop'; -type DragAndDropParams = { - dropZone: React.MutableRefObject; - onDrop?: (event: DragEvent) => void; - shouldAllowDrop?: boolean; - isDisabled?: boolean; - shouldAcceptDrop?: (event: DragEvent) => boolean; -}; - -type DragAndDropOptions = { - isDraggingOver: boolean; -}; - /** * @param dropZone – ref to the dropZone component */ -export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllowDrop = true, isDisabled = false, shouldAcceptDrop = () => true}: DragAndDropParams): DragAndDropOptions { +const useDragAndDrop: UseDragAndDrop = ({dropZone, onDrop = () => {}, shouldAllowDrop = true, isDisabled = false, shouldAcceptDrop = () => true}) => { const isFocused = useIsFocused(); const [isDraggingOver, setIsDraggingOver] = useState(false); const {close: closePopover} = useContext(PopoverContext); @@ -111,7 +98,7 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow return; } - const dropZoneRef = dropZone.current as HTMLDivElement; + const dropZoneRef = dropZone.current; // Note that the dragover event needs to be called with `event.preventDefault` in order for the drop event to be fired: // https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome @@ -133,4 +120,6 @@ export default function useDragAndDrop({dropZone, onDrop = () => {}, shouldAllow }, [dropZone, dropZoneDragHandler]); return {isDraggingOver}; -} +}; + +export default useDragAndDrop; diff --git a/src/hooks/useDragAndDrop/types.ts b/src/hooks/useDragAndDrop/types.ts new file mode 100644 index 000000000000..78859783ee36 --- /dev/null +++ b/src/hooks/useDragAndDrop/types.ts @@ -0,0 +1,15 @@ +type DragAndDropParams = { + dropZone: React.MutableRefObject; + onDrop?: (event: DragEvent) => void; + shouldAllowDrop?: boolean; + isDisabled?: boolean; + shouldAcceptDrop?: (event: DragEvent) => boolean; +}; + +type DragAndDropResult = { + isDraggingOver: boolean; +}; + +type UseDragAndDrop = (params: DragAndDropParams) => DragAndDropResult; + +export default UseDragAndDrop; diff --git a/src/hooks/useSyncFocus/index.ts b/src/hooks/useSyncFocus/index.ts index 2dd0fef70cf7..afc4e7e351ab 100644 --- a/src/hooks/useSyncFocus/index.ts +++ b/src/hooks/useSyncFocus/index.ts @@ -8,7 +8,7 @@ import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionSt * When the user navigates through the app using the arrows and then the tab button, the focus on the element and the native focus of the browser differs. * To maintain consistency when an element is focused in the app, the focus() method is additionally called on the focused element to eliminate the difference between native browser focus and application focus. */ -const useSyncFocus = (ref: RefObject, isFocused: boolean, shouldSyncFocus = true) => { +const useSyncFocus = (ref: RefObject, isFocused: boolean, shouldSyncFocus = true) => { const {didScreenTransitionEnd} = useScreenWrapperTranstionStatus(); useLayoutEffect(() => { diff --git a/src/libs/ComposerFocusManager.ts b/src/libs/ComposerFocusManager.ts index d793c202d243..7dc2296a5e10 100644 --- a/src/libs/ComposerFocusManager.ts +++ b/src/libs/ComposerFocusManager.ts @@ -1,4 +1,3 @@ -import type {View} from 'react-native'; import {TextInput} from 'react-native'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; @@ -10,8 +9,6 @@ type InputElement = (TextInput & HTMLElement) | null; type RestoreFocusType = ValueOf | undefined; -type ModalContainer = (View & HTMLElement) | undefined | null; - /** * So far, modern browsers only support the file cancel event in some newer versions * (i.e., Chrome: 113+ / Firefox: 91+ / Safari 16.4+), and there is no standard feature detection method available. @@ -89,7 +86,7 @@ function getId() { /** * Save the focus state when opening the modal. */ -function saveFocusState(id: ModalId, isInUploadingContext = false, shouldClearFocusWithType = false, container: ModalContainer = undefined) { +function saveFocusState(id: ModalId, isInUploadingContext = false, shouldClearFocusWithType = false) { const activeInput = getActiveInput(); // For popoverWithoutOverlay, react calls autofocus before useEffect. @@ -108,9 +105,6 @@ function saveFocusState(id: ModalId, isInUploadingContext = false, shouldClearFo }); } - if (container?.contains(input)) { - return; - } focusMap.set(id, {input, isInUploadingContext}); input?.blur(); } diff --git a/src/libs/focusTextInputAfterAnimation/types.ts b/src/libs/focusTextInputAfterAnimation/types.ts index bfe29317c1ef..ce5086328b33 100644 --- a/src/libs/focusTextInputAfterAnimation/types.ts +++ b/src/libs/focusTextInputAfterAnimation/types.ts @@ -1,5 +1,5 @@ import type {TextInput} from 'react-native'; -type FocusTextInputAfterAnimation = (inputRef: TextInput | HTMLInputElement | undefined, animationLength: number) => void; +type FocusTextInputAfterAnimation = (inputRef: TextInput | HTMLElement | undefined, animationLength: number) => void; export default FocusTextInputAfterAnimation; diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 5697807ca825..10d68504f100 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -201,7 +201,7 @@ function ReportActionItem({ const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); const reactionListRef = useContext(ReactionListContext); const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); - const textInputRef = useRef(); + const textInputRef = useRef(); const popoverAnchorRef = useRef>(null); const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(draftMessage); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index 5fb23be145fb..abed8084808f 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -73,7 +73,7 @@ const shouldUseForcedSelectionRange = shouldUseEmojiPickerSelection(); function ReportActionItemMessageEdit( {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE}: ReportActionItemMessageEditProps, - forwardedRef: ForwardedRef<(TextInput & HTMLTextAreaElement) | undefined>, + forwardedRef: ForwardedRef, ) { const theme = useTheme(); const styles = useThemeStyles(); diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 0d723961a29e..39a5c368d7a1 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -87,7 +87,6 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, const prevPersonalDetails = usePrevious(personalDetails); const {translate, formatPhoneNumber, preferredLocale} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); - const dropdownButtonRef = useRef(null); const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); const isLoading = useMemo( () => !isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policy?.employeeList)), @@ -504,7 +503,6 @@ function WorkspaceMembersPage({personalDetails, invitedEmailsToAccountIDsDraft, buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} onPress={() => null} options={getBulkActionsButtonOptions()} - buttonRef={dropdownButtonRef} isSplitButton={false} style={[isSmallScreenWidth && styles.flexGrow1]} /> diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 12606a9284fd..eb7af7bd6458 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -1,6 +1,6 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -51,7 +51,6 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { const theme = useTheme(); const {translate} = useLocalize(); const [selectedCategories, setSelectedCategories] = useState>({}); - const dropdownButtonRef = useRef(null); const [deleteCategoriesConfirmModalVisible, setDeleteCategoriesConfirmModalVisible] = useState(false); const isFocused = useIsFocused(); const {environmentURL} = useEnvironment(); @@ -205,7 +204,6 @@ function WorkspaceCategoriesPage({route}: WorkspaceCategoriesPageProps) { return ( null} shouldAlwaysShowDropdownMenu pressOnEnter diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index 85f90c54a359..5e52fdb64e77 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -1,6 +1,6 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -52,7 +52,6 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) const [selectedDistanceRates, setSelectedDistanceRates] = useState([]); const [isWarningModalVisible, setIsWarningModalVisible] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); - const dropdownButtonRef = useRef(null); const policyID = route.params.policyID; const isFocused = useIsFocused(); @@ -255,7 +254,6 @@ function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} onPress={() => null} options={getBulkActionsButtonOptions()} - buttonRef={dropdownButtonRef} style={[isSmallScreenWidth && styles.flexGrow1]} wrapperStyle={styles.w100} isSplitButton={false} diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 0e4290776250..6850da136a65 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -1,7 +1,7 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; import lodashSortBy from 'lodash/sortBy'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; @@ -62,7 +62,6 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { const theme = useTheme(); const {translate} = useLocalize(); const [selectedTags, setSelectedTags] = useState>({}); - const dropdownButtonRef = useRef(null); const [deleteTagsConfirmModalVisible, setDeleteTagsConfirmModalVisible] = useState(false); const isFocused = useIsFocused(); const policyID = route.params.policyID ?? ''; @@ -223,7 +222,6 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { return ( null} shouldAlwaysShowDropdownMenu pressOnEnter diff --git a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx index 91d54cb02055..646e119b9e65 100644 --- a/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx +++ b/src/pages/workspace/taxes/WorkspaceTaxesPage.tsx @@ -1,6 +1,6 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import Button from '@components/Button'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; @@ -51,7 +51,6 @@ function WorkspaceTaxesPage({ const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const defaultExternalID = policy?.taxRates?.defaultExternalID; const foreignTaxDefault = policy?.taxRates?.foreignTaxDefault; - const dropdownButtonRef = useRef(null); const isFocused = useIsFocused(); const fetchTaxes = useCallback(() => { @@ -224,7 +223,6 @@ function WorkspaceTaxesPage({ ) : ( - buttonRef={dropdownButtonRef} onPress={() => {}} options={dropdownMenuOptions} buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} diff --git a/src/types/utils/htmlDivElementRef.ts b/src/types/utils/htmlDivElementRef.ts new file mode 100644 index 000000000000..9a20bb87e32e --- /dev/null +++ b/src/types/utils/htmlDivElementRef.ts @@ -0,0 +1,5 @@ +import type {View} from 'react-native'; + +const htmlDivElementRef = (ref: React.RefObject) => ref as React.RefObject; + +export default htmlDivElementRef; diff --git a/src/types/utils/textRef.ts b/src/types/utils/textRef.ts index 668f54d59e36..ecc22c8b013a 100644 --- a/src/types/utils/textRef.ts +++ b/src/types/utils/textRef.ts @@ -1,6 +1,6 @@ // eslint-disable-next-line no-restricted-imports import type {Text} from 'react-native'; -const textRef = (ref: React.RefObject) => ref as React.RefObject; +const textRef = (ref: React.RefObject) => ref as React.RefObject; export default textRef; diff --git a/src/types/utils/viewForwardedRef.ts b/src/types/utils/viewForwardedRef.ts index 87b26bfbab40..bd6201d4321e 100644 --- a/src/types/utils/viewForwardedRef.ts +++ b/src/types/utils/viewForwardedRef.ts @@ -1,6 +1,6 @@ import type {ForwardedRef} from 'react'; import type {View} from 'react-native'; -const viewForwardedRef = (ref: ForwardedRef) => ref as ForwardedRef; +const viewForwardedRef = (ref: ForwardedRef) => ref as ForwardedRef; export default viewForwardedRef; diff --git a/src/types/utils/viewRef.ts b/src/types/utils/viewRef.ts index 1fd9d186ba1f..41d7cfac5b62 100644 --- a/src/types/utils/viewRef.ts +++ b/src/types/utils/viewRef.ts @@ -1,5 +1,5 @@ import type {View} from 'react-native'; -const viewRef = (ref: React.RefObject) => ref as React.RefObject; +const viewRef = (ref: React.RefObject) => ref as React.RefObject; export default viewRef;