diff --git a/Mobile-Expensify b/Mobile-Expensify index 6c6f3b2553eb9..dd19b8f51ec16 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 6c6f3b2553eb9911633e20ba88afeb55b6f04cbb +Subproject commit dd19b8f51ec16bb266e7daf794b4b0e0ce10bd8b diff --git a/android/app/build.gradle b/android/app/build.gradle index 5d0747c79ec4e..02133ba8d5b28 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -114,8 +114,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009022703 - versionName "9.2.27-3" + versionCode 1009022704 + versionName "9.2.27-4" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 713c1bbf59593..662f96945cf20 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -44,7 +44,7 @@ CFBundleVersion - 9.2.27.3 + 9.2.27.4 FullStory OrgId diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 02caaed6bd060..dfa3be6a8d1a2 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.2.27 CFBundleVersion - 9.2.27.3 + 9.2.27.4 NSExtension NSExtensionPointIdentifier diff --git a/ios/ShareViewController/Info.plist b/ios/ShareViewController/Info.plist index f64038acabed0..10b5b84738bb2 100644 --- a/ios/ShareViewController/Info.plist +++ b/ios/ShareViewController/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.2.27 CFBundleVersion - 9.2.27.3 + 9.2.27.4 NSExtension NSExtensionAttributes diff --git a/package-lock.json b/package-lock.json index 936b9e3112b5b..e19d27bbc26ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.2.27-3", + "version": "9.2.27-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.2.27-3", + "version": "9.2.27-4", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 0f4e1b0e469a4..c639aad6979c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.2.27-3", + "version": "9.2.27-4", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/components/ActionSheetAwareScrollView/index.ios.tsx b/src/components/ActionSheetAwareScrollView/index.ios.tsx index 9fa26e898d6ec..6fead4946aea3 100644 --- a/src/components/ActionSheetAwareScrollView/index.ios.tsx +++ b/src/components/ActionSheetAwareScrollView/index.ios.tsx @@ -1,10 +1,12 @@ -import React, {useCallback} from 'react'; +import React, {forwardRef, useCallback} from 'react'; +// eslint-disable-next-line no-restricted-imports +import type {ScrollView} from 'react-native'; import Reanimated, {useAnimatedRef, useAnimatedStyle} from 'react-native-reanimated'; import {Actions, ActionSheetAwareScrollViewContext, ActionSheetAwareScrollViewProvider} from './ActionSheetAwareScrollViewContext'; import type {ActionSheetAwareScrollViewProps, RenderActionSheetAwareScrollViewComponent} from './types'; import useActionSheetKeyboardSpacing from './useActionSheetKeyboardSpacing'; -function ActionSheetAwareScrollView({style, children, ref, ...props}: ActionSheetAwareScrollViewProps) { +const ActionSheetAwareScrollView = forwardRef(({style, children, ...props}, ref) => { const scrollViewAnimatedRef = useAnimatedRef(); const onRef = useCallback( @@ -36,7 +38,7 @@ function ActionSheetAwareScrollView({style, children, ref, ...props}: ActionShee {children} ); -} +}); export default ActionSheetAwareScrollView; diff --git a/src/components/ActionSheetAwareScrollView/index.tsx b/src/components/ActionSheetAwareScrollView/index.tsx index aad22519466e2..f2fc64cda8bd0 100644 --- a/src/components/ActionSheetAwareScrollView/index.tsx +++ b/src/components/ActionSheetAwareScrollView/index.tsx @@ -1,21 +1,20 @@ // this whole file is just for other platforms // iOS version has everything implemented -import React from 'react'; +import React, {forwardRef} from 'react'; // eslint-disable-next-line no-restricted-imports import {ScrollView} from 'react-native'; import {Actions, ActionSheetAwareScrollViewContext, ActionSheetAwareScrollViewProvider} from './ActionSheetAwareScrollViewContext'; import type {ActionSheetAwareScrollViewProps, RenderActionSheetAwareScrollViewComponent} from './types'; -function ActionSheetAwareScrollView(props: ActionSheetAwareScrollViewProps) { - return ( - - {props.children} - - ); -} +const ActionSheetAwareScrollView = forwardRef((props, ref) => ( + + {props.children} + +)); export default ActionSheetAwareScrollView; diff --git a/src/components/ActionSheetAwareScrollView/types.ts b/src/components/ActionSheetAwareScrollView/types.ts index c3b1a2ded8c9a..9d7f7f765fefc 100644 --- a/src/components/ActionSheetAwareScrollView/types.ts +++ b/src/components/ActionSheetAwareScrollView/types.ts @@ -1,10 +1,6 @@ -import type {PropsWithChildren, Ref} from 'react'; -// eslint-disable-next-line no-restricted-imports -import type {ScrollView, ScrollViewProps} from 'react-native'; +import type {PropsWithChildren} from 'react'; +import type {ScrollViewProps} from 'react-native'; -type ActionSheetAwareScrollViewAnimationProps = { - ref?: Ref; -}; -type ActionSheetAwareScrollViewProps = PropsWithChildren & ActionSheetAwareScrollViewAnimationProps; +type ActionSheetAwareScrollViewProps = PropsWithChildren; type RenderActionSheetAwareScrollViewComponent = ((props: ActionSheetAwareScrollViewProps) => React.ReactElement) | undefined; export type {ActionSheetAwareScrollViewProps, RenderActionSheetAwareScrollViewComponent}; diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx index ac27c32700b3a..4e0fe3d646f7b 100644 --- a/src/components/AddressSearch/index.tsx +++ b/src/components/AddressSearch/index.tsx @@ -1,4 +1,5 @@ -import React, {useEffect, useMemo, useRef, useState} from 'react'; +import React, {forwardRef, useEffect, useMemo, useRef, useState} from 'react'; +import type {ForwardedRef} from 'react'; import {Keyboard, LogBox, View} from 'react-native'; import type {LayoutChangeEvent} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; @@ -48,38 +49,40 @@ function isPlaceMatchForSearch(search: string, place: PredefinedPlace): boolean // VirtualizedList component with a VirtualizedList-backed instead LogBox.ignoreLogs(['VirtualizedLists should never be nested']); -function AddressSearch({ - canUseCurrentLocation = false, - containerStyles, - defaultValue, - errorText = '', - hint = '', - inputID, - limitSearchesToCountry, - label, - maxInputLength, - onFocus, - onBlur, - onInputChange, - onPress, - onCountryChange, - predefinedPlaces = [], - renamedInputKeys = { - street: 'addressStreet', - street2: 'addressStreet2', - city: 'addressCity', - state: 'addressState', - zipCode: 'addressZipCode', - lat: 'addressLat', - lng: 'addressLng', - }, - resultTypes = 'address', - shouldSaveDraft = false, - value, - locationBias, - caretHidden, - ref, -}: AddressSearchProps) { +function AddressSearch( + { + canUseCurrentLocation = false, + containerStyles, + defaultValue, + errorText = '', + hint = '', + inputID, + limitSearchesToCountry, + label, + maxInputLength, + onFocus, + onBlur, + onInputChange, + onPress, + onCountryChange, + predefinedPlaces = [], + renamedInputKeys = { + street: 'addressStreet', + street2: 'addressStreet2', + city: 'addressCity', + state: 'addressState', + zipCode: 'addressZipCode', + lat: 'addressLat', + lng: 'addressLng', + }, + resultTypes = 'address', + shouldSaveDraft = false, + value, + locationBias, + caretHidden, + }: AddressSearchProps, + ref: ForwardedRef, +) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -487,6 +490,6 @@ function AddressSearch({ AddressSearch.displayName = 'AddressSearchWithRef'; -export default AddressSearch; +export default forwardRef(AddressSearch); export type {AddressSearchProps}; diff --git a/src/components/AddressSearch/types.ts b/src/components/AddressSearch/types.ts index 4c2979e9427ff..dcf8822c4b5e5 100644 --- a/src/components/AddressSearch/types.ts +++ b/src/components/AddressSearch/types.ts @@ -1,4 +1,4 @@ -import type {ForwardedRef, RefObject} from 'react'; +import type {RefObject} from 'react'; import type {NativeSyntheticEvent, StyleProp, TextInputFocusEventData, View, ViewStyle} from 'react-native'; import type {Place} from 'react-native-google-places-autocomplete'; import type {Country} from '@src/CONST'; @@ -90,9 +90,6 @@ type AddressSearchProps = { /** If true, caret is hidden. The default value is false. */ caretHidden?: boolean; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; type IsCurrentTargetInsideContainerType = (event: FocusEvent | NativeSyntheticEvent, containerRef: RefObject) => boolean; diff --git a/src/components/AmountPicker/index.tsx b/src/components/AmountPicker/index.tsx index 40ad6ed9b752f..ec9ff2f4117dc 100644 --- a/src/components/AmountPicker/index.tsx +++ b/src/components/AmountPicker/index.tsx @@ -1,4 +1,5 @@ -import React, {useState} from 'react'; +import React, {forwardRef, useState} from 'react'; +import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import blurActiveElement from '@libs/Accessibility/blurActiveElement'; @@ -7,7 +8,7 @@ import callOrReturn from '@src/types/utils/callOrReturn'; import AmountSelectorModal from './AmountSelectorModal'; import type {AmountPickerProps} from './types'; -function AmountPicker({value, description, title, errorText = '', onInputChange, furtherDetails, rightLabel, ref, ...rest}: AmountPickerProps) { +function AmountPicker({value, description, title, errorText = '', onInputChange, furtherDetails, rightLabel, ...rest}: AmountPickerProps, forwardedRef: ForwardedRef) { const [isPickerVisible, setIsPickerVisible] = useState(false); const showPickerModal = () => { @@ -30,7 +31,7 @@ function AmountPicker({value, description, title, errorText = '', onInputChange, return ( ; } & Pick & Pick< NumberWithSymbolFormProps, diff --git a/src/components/AmountWithoutCurrencyInput.tsx b/src/components/AmountWithoutCurrencyInput.tsx index 211d4050e40ff..da1f3855dcf17 100644 --- a/src/components/AmountWithoutCurrencyInput.tsx +++ b/src/components/AmountWithoutCurrencyInput.tsx @@ -1,9 +1,10 @@ import React, {useCallback, useMemo} from 'react'; +import type {ForwardedRef} from 'react'; import useLocalize from '@hooks/useLocalize'; import getAmountInputKeyboard from '@libs/getAmountInputKeyboard'; import {replaceAllDigits, replaceCommasWithPeriod, stripSpacesFromAmount} from '@libs/MoneyRequestUtils'; import TextInput from './TextInput'; -import type {BaseTextInputProps} from './TextInput/BaseTextInput/types'; +import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types'; type AmountFormProps = { /** Amount supplied by the FormProvider */ @@ -16,19 +17,10 @@ type AmountFormProps = { shouldAllowNegative?: boolean; } & Partial; -function AmountWithoutCurrencyInput({ - value: amount, - shouldAllowNegative = false, - inputID, - name, - defaultValue, - accessibilityLabel, - role, - label, - onInputChange, - ref, - ...rest -}: AmountFormProps) { +function AmountWithoutCurrencyInput( + {value: amount, shouldAllowNegative = false, inputID, name, defaultValue, accessibilityLabel, role, label, onInputChange, ...rest}: AmountFormProps, + ref: ForwardedRef, +) { const {toLocaleDigit} = useLocalize(); const separator = useMemo( () => @@ -95,4 +87,4 @@ function AmountWithoutCurrencyInput({ AmountWithoutCurrencyInput.displayName = 'AmountWithoutCurrencyInput'; -export default AmountWithoutCurrencyInput; +export default React.forwardRef(AmountWithoutCurrencyInput); diff --git a/src/components/Attachments/AttachmentCarousel/Pager/index.tsx b/src/components/Attachments/AttachmentCarousel/Pager/index.tsx index 4ef7d53d866f7..11875d93473fd 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/Pager/index.tsx @@ -48,12 +48,12 @@ type AttachmentCarouselPagerProps = { /** Callback for attachment errors */ onAttachmentError?: (source: AttachmentSource) => void; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; -function AttachmentCarouselPager({items, activeAttachmentID, initialPage, setShouldShowArrows, onPageSelected, onClose, reportID, onAttachmentError, ref}: AttachmentCarouselPagerProps) { +function AttachmentCarouselPager( + {items, activeAttachmentID, initialPage, setShouldShowArrows, onPageSelected, onClose, reportID, onAttachmentError}: AttachmentCarouselPagerProps, + ref: ForwardedRef, +) { const {handleTap, handleScaleChange, isScrollEnabled} = useCarouselContextEvents(setShouldShowArrows); const styles = useThemeStyles(); const pagerRef = useRef(null); @@ -153,5 +153,5 @@ function AttachmentCarouselPager({items, activeAttachmentID, initialPage, setSho } AttachmentCarouselPager.displayName = 'AttachmentCarouselPager'; -export default AttachmentCarouselPager; +export default React.forwardRef(AttachmentCarouselPager); export type {AttachmentCarouselPagerHandle}; diff --git a/src/components/CheckboxWithLabel.tsx b/src/components/CheckboxWithLabel.tsx index b5ef4f2e974d8..0647b495bd339 100644 --- a/src/components/CheckboxWithLabel.tsx +++ b/src/components/CheckboxWithLabel.tsx @@ -58,23 +58,12 @@ type CheckboxWithLabelProps = RequiredLabelProps & { /** An accessibility label for the checkbox */ accessibilityLabel?: string; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; -function CheckboxWithLabel({ - errorText = '', - isChecked: isCheckedProp = false, - defaultValue = false, - onInputChange = () => {}, - LabelComponent, - label, - accessibilityLabel, - style, - value, - ref, -}: CheckboxWithLabelProps) { +function CheckboxWithLabel( + {errorText = '', isChecked: isCheckedProp = false, defaultValue = false, onInputChange = () => {}, LabelComponent, label, accessibilityLabel, style, value}: CheckboxWithLabelProps, + ref: ForwardedRef, +) { const styles = useThemeStyles(); // We need to pick the first value that is strictly a boolean // https://github.com/Expensify/App/issues/16885#issuecomment-1520846065 @@ -117,6 +106,6 @@ function CheckboxWithLabel({ CheckboxWithLabel.displayName = 'CheckboxWithLabel'; -export default CheckboxWithLabel; +export default React.forwardRef(CheckboxWithLabel); export type {CheckboxWithLabelProps}; diff --git a/src/components/ContextMenuItem.tsx b/src/components/ContextMenuItem.tsx index 214e7bd616ec3..6aff89666a079 100644 --- a/src/components/ContextMenuItem.tsx +++ b/src/components/ContextMenuItem.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef} from 'react'; -import React, {useImperativeHandle} from 'react'; +import React, {forwardRef, useImperativeHandle} from 'react'; import type {GestureResponderEvent, StyleProp, View, ViewStyle} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -61,35 +61,34 @@ type ContextMenuItemProps = { /** Whether the menu item should show loading icon */ shouldShowLoadingSpinnerIcon?: boolean; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; type ContextMenuItemHandle = { triggerPressAndUpdateSuccess?: () => void; }; -function ContextMenuItem({ - onPress, - successIcon, - successText = '', - icon, - text, - isMini = false, - description = '', - isAnonymousAction = false, - isFocused = false, - shouldLimitWidth = true, - wrapperStyle, - shouldPreventDefaultFocusOnPress = true, - buttonRef = {current: null}, - onFocus = () => {}, - onBlur = () => {}, - disabled = false, - shouldShowLoadingSpinnerIcon = false, - ref, -}: ContextMenuItemProps) { +function ContextMenuItem( + { + onPress, + successIcon, + successText = '', + icon, + text, + isMini = false, + description = '', + isAnonymousAction = false, + isFocused = false, + shouldLimitWidth = true, + wrapperStyle, + shouldPreventDefaultFocusOnPress = true, + buttonRef = {current: null}, + onFocus = () => {}, + onBlur = () => {}, + disabled = false, + shouldShowLoadingSpinnerIcon = false, + }: ContextMenuItemProps, + ref: ForwardedRef, +) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {windowWidth} = useWindowDimensions(); @@ -152,5 +151,5 @@ function ContextMenuItem({ ContextMenuItem.displayName = 'ContextMenuItem'; -export default ContextMenuItem; +export default forwardRef(ContextMenuItem); export type {ContextMenuItemHandle}; diff --git a/src/components/CountrySelector.tsx b/src/components/CountrySelector.tsx index 817c49ad03bc1..094f529c66b2e 100644 --- a/src/components/CountrySelector.tsx +++ b/src/components/CountrySelector.tsx @@ -1,5 +1,5 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {useEffect, useRef} from 'react'; +import React, {forwardRef, useEffect, useRef} from 'react'; import type {ForwardedRef} from 'react'; import type {View} from 'react-native'; import useGeographicalStateAndCountryFromRoute from '@hooks/useGeographicalStateAndCountryFromRoute'; @@ -27,12 +27,9 @@ type CountrySelectorProps = { /** Callback to call when the picker modal is dismissed */ onBlur?: () => void; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; -function CountrySelector({errorText = '', value: countryCode, onInputChange = () => {}, onBlur, ref}: CountrySelectorProps) { +function CountrySelector({errorText = '', value: countryCode, onInputChange = () => {}, onBlur}: CountrySelectorProps, ref: ForwardedRef) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {country: countryFromUrl} = useGeographicalStateAndCountryFromRoute(); @@ -88,4 +85,4 @@ function CountrySelector({errorText = '', value: countryCode, onInputChange = () CountrySelector.displayName = 'CountrySelector'; -export default CountrySelector; +export default forwardRef(CountrySelector); diff --git a/src/components/CurrencySelector.tsx b/src/components/CurrencySelector.tsx index 5c242c7bdc7d3..1d45860f5d32b 100644 --- a/src/components/CurrencySelector.tsx +++ b/src/components/CurrencySelector.tsx @@ -1,6 +1,6 @@ import {useIsFocused} from '@react-navigation/native'; import type {ForwardedRef} from 'react'; -import React, {useEffect, useRef} from 'react'; +import React, {forwardRef, useEffect, useRef} from 'react'; import type {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -35,21 +35,20 @@ type CurrencySelectorProps = { /** Whether to show currency symbol in the title */ shouldShowCurrencySymbol?: boolean; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; -function CurrencySelector({ - errorText = '', - value: currency, - onInputChange = () => {}, - onBlur, - currencySelectorRoute = ROUTES.SETTINGS_CHANGE_CURRENCY, - label, - shouldShowCurrencySymbol = false, - ref, -}: CurrencySelectorProps) { +function CurrencySelector( + { + errorText = '', + value: currency, + onInputChange = () => {}, + onBlur, + currencySelectorRoute = ROUTES.SETTINGS_CHANGE_CURRENCY, + label, + shouldShowCurrencySymbol = false, + }: CurrencySelectorProps, + ref: ForwardedRef, +) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -95,4 +94,4 @@ function CurrencySelector({ CurrencySelector.displayName = 'CurrencySelector'; -export default CurrencySelector; +export default forwardRef(CurrencySelector); diff --git a/src/components/DatePicker/index.tsx b/src/components/DatePicker/index.tsx index 3704be90fd263..e3855f930347a 100644 --- a/src/components/DatePicker/index.tsx +++ b/src/components/DatePicker/index.tsx @@ -1,5 +1,6 @@ import {format, setYear} from 'date-fns'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import type {ForwardedRef} from 'react'; import {InteractionManager, View} from 'react-native'; import * as Expensicons from '@components/Icon/Expensicons'; import TextInput from '@components/TextInput'; @@ -15,24 +16,26 @@ import type {DateInputWithPickerProps} from './types'; const PADDING_MODAL_DATE_PICKER = 8; -function DatePicker({ - defaultValue, - disabled, - errorText, - inputID, - label, - minDate = setYear(new Date(), CONST.CALENDAR_PICKER.MIN_YEAR), - maxDate = setYear(new Date(), CONST.CALENDAR_PICKER.MAX_YEAR), - onInputChange, - onTouched = () => {}, - placeholder, - value, - shouldSaveDraft = false, - formID, - autoFocus = false, - shouldHideClearButton = false, - ref, -}: DateInputWithPickerProps) { +function DatePicker( + { + defaultValue, + disabled, + errorText, + inputID, + label, + minDate = setYear(new Date(), CONST.CALENDAR_PICKER.MIN_YEAR), + maxDate = setYear(new Date(), CONST.CALENDAR_PICKER.MAX_YEAR), + onInputChange, + onTouched = () => {}, + placeholder, + value, + shouldSaveDraft = false, + formID, + autoFocus = false, + shouldHideClearButton = false, + }: DateInputWithPickerProps, + ref: ForwardedRef, +) { const styles = useThemeStyles(); const {windowHeight, windowWidth} = useWindowDimensions(); const {translate} = useLocalize(); @@ -162,4 +165,4 @@ function DatePicker({ DatePicker.displayName = 'DatePicker'; -export default DatePicker; +export default forwardRef(DatePicker); diff --git a/src/components/DatePicker/types.ts b/src/components/DatePicker/types.ts index 24ef3e61f7062..1e4bafa5f1d90 100644 --- a/src/components/DatePicker/types.ts +++ b/src/components/DatePicker/types.ts @@ -1,6 +1,5 @@ -import type {ForwardedRef} from 'react'; import type PopoverWithMeasuredContentProps from '@components/PopoverWithMeasuredContent/types'; -import type {BaseTextInputProps, BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; +import type {BaseTextInputProps} from '@components/TextInput/BaseTextInput/types'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; type DatePickerBaseProps = { @@ -54,11 +53,6 @@ type DateInputWithPickerProps = DatePickerBaseProps & * @default false */ shouldHideClearButton?: boolean; - - /** - * Reference to the outer element - */ - ref?: ForwardedRef; }; type DatePickerProps = { diff --git a/src/components/FormScrollView.tsx b/src/components/FormScrollView.tsx index 77eed896a9689..91f5a825a38a1 100644 --- a/src/components/FormScrollView.tsx +++ b/src/components/FormScrollView.tsx @@ -8,12 +8,9 @@ import ScrollView from './ScrollView'; type FormScrollViewProps = ScrollViewProps & { /** Form elements */ children: React.ReactNode; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; -function FormScrollView({children, ref, ...rest}: FormScrollViewProps) { +function FormScrollView({children, ...rest}: FormScrollViewProps, ref: ForwardedRef) { const styles = useThemeStyles(); return ( ; }; -function HighlightableMenuItem({wrapperStyle, highlighted, ref, ...restOfProps}: Props) { +function HighlightableMenuItem({wrapperStyle, highlighted, ...restOfProps}: Props, ref: ForwardedRef) { const styles = useThemeStyles(); const flattenedWrapperStyles = StyleSheet.flatten(wrapperStyle); @@ -38,4 +35,4 @@ function HighlightableMenuItem({wrapperStyle, highlighted, ref, ...restOfProps}: HighlightableMenuItem.displayName = 'HighlightableMenuItem'; -export default HighlightableMenuItem; +export default forwardRef(HighlightableMenuItem); diff --git a/src/components/InteractiveStepSubHeader.tsx b/src/components/InteractiveStepSubHeader.tsx index 3be92324e73ae..aad5f502df478 100644 --- a/src/components/InteractiveStepSubHeader.tsx +++ b/src/components/InteractiveStepSubHeader.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef} from 'react'; -import React, {useImperativeHandle, useState} from 'react'; +import React, {forwardRef, useImperativeHandle, useState} from 'react'; import type {ViewStyle} from 'react-native'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -20,9 +20,6 @@ type InteractiveStepSubHeaderProps = { /** The index of the step to start with */ startStepIndex?: number; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; type InteractiveStepSubHeaderHandle = { @@ -39,7 +36,7 @@ type InteractiveStepSubHeaderHandle = { const MIN_AMOUNT_FOR_EXPANDING = 3; const MIN_AMOUNT_OF_STEPS = 2; -function InteractiveStepSubHeader({stepNames, startStepIndex = 0, onStepSelected, ref}: InteractiveStepSubHeaderProps) { +function InteractiveStepSubHeader({stepNames, startStepIndex = 0, onStepSelected}: InteractiveStepSubHeaderProps, ref: ForwardedRef) { const styles = useThemeStyles(); const containerWidthStyle: ViewStyle = stepNames.length < MIN_AMOUNT_FOR_EXPANDING ? styles.mnw60 : styles.mnw100; @@ -126,4 +123,4 @@ InteractiveStepSubHeader.displayName = 'InteractiveStepSubHeader'; export type {InteractiveStepSubHeaderProps, InteractiveStepSubHeaderHandle}; -export default InteractiveStepSubHeader; +export default forwardRef(InteractiveStepSubHeader); diff --git a/src/components/InteractiveStepWrapper.tsx b/src/components/InteractiveStepWrapper.tsx index dc5b54669509b..d9f6709c47ec8 100644 --- a/src/components/InteractiveStepWrapper.tsx +++ b/src/components/InteractiveStepWrapper.tsx @@ -1,5 +1,4 @@ -import type {ForwardedRef} from 'react'; -import React from 'react'; +import React, {forwardRef} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -57,28 +56,27 @@ type InteractiveStepWrapperProps = { * This flag can be removed, once all components/screens have switched to edge-to-edge safe area handling. */ enableEdgeToEdgeBottomSafeAreaPadding?: boolean; - - // Reference to the outer element - ref?: ForwardedRef; }; -function InteractiveStepWrapper({ - children, - wrapperID, - handleBackButtonPress, - headerTitle, - headerSubtitle, - startStepIndex, - stepNames, - shouldEnableMaxHeight, - shouldShowOfflineIndicator, - shouldShowOfflineIndicatorInWideScreen, - shouldEnablePickerAvoiding = false, - offlineIndicatorStyle, - shouldKeyboardOffsetBottomSafeAreaPadding, - enableEdgeToEdgeBottomSafeAreaPadding, - ref, -}: InteractiveStepWrapperProps) { +function InteractiveStepWrapper( + { + children, + wrapperID, + handleBackButtonPress, + headerTitle, + headerSubtitle, + startStepIndex, + stepNames, + shouldEnableMaxHeight, + shouldShowOfflineIndicator, + shouldShowOfflineIndicatorInWideScreen, + shouldEnablePickerAvoiding = false, + offlineIndicatorStyle, + shouldKeyboardOffsetBottomSafeAreaPadding, + enableEdgeToEdgeBottomSafeAreaPadding, + }: InteractiveStepWrapperProps, + ref: React.ForwardedRef, +) { const styles = useThemeStyles(); return ( @@ -114,4 +112,4 @@ function InteractiveStepWrapper({ InteractiveStepWrapper.displayName = 'InteractiveStepWrapper'; -export default InteractiveStepWrapper; +export default forwardRef(InteractiveStepWrapper); diff --git a/src/components/MagicCodeInput.tsx b/src/components/MagicCodeInput.tsx index dea019ea1cd0d..12946ee83693f 100644 --- a/src/components/MagicCodeInput.tsx +++ b/src/components/MagicCodeInput.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef, KeyboardEvent} from 'react'; -import React, {useEffect, useImperativeHandle, useRef, useState} from 'react'; +import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react'; import type {NativeSyntheticEvent, TextInput as RNTextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; import {StyleSheet, View} from 'react-native'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; @@ -99,9 +99,6 @@ type MagicCodeInputProps = { /** TestID for test */ testID?: string; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; type MagicCodeInputHandle = { @@ -135,23 +132,25 @@ const composeToString = (value: string[]): string => value.map((v) => v ?? CONST const getInputPlaceholderSlots = (length: number): number[] => Array.from(Array(length).keys()); -function MagicCodeInput({ - value = '', - name = '', - autoFocus = true, - errorText = '', - shouldSubmitOnComplete = true, - onChangeText: onChangeTextProp = () => {}, - onFocus: onFocusProps, - maxLength = CONST.MAGIC_CODE_LENGTH, - onFulfill = () => {}, - isDisableKeyboard = false, - lastPressedDigit = '', - autoComplete, - hasError = false, - testID = '', - ref, -}: MagicCodeInputProps) { +function MagicCodeInput( + { + value = '', + name = '', + autoFocus = true, + errorText = '', + shouldSubmitOnComplete = true, + onChangeText: onChangeTextProp = () => {}, + onFocus: onFocusProps, + maxLength = CONST.MAGIC_CODE_LENGTH, + onFulfill = () => {}, + isDisableKeyboard = false, + lastPressedDigit = '', + autoComplete, + hasError = false, + testID = '', + }: MagicCodeInputProps, + ref: ForwardedRef, +) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const inputRef = useRef(null); @@ -541,5 +540,5 @@ function MagicCodeInput({ MagicCodeInput.displayName = 'MagicCodeInput'; -export default MagicCodeInput; +export default forwardRef(MagicCodeInput); export type {AutoCompleteVariant, MagicCodeInputHandle, MagicCodeInputProps}; diff --git a/src/components/PercentageForm.tsx b/src/components/PercentageForm.tsx index 3c3b78e3ad825..aee231de526ce 100644 --- a/src/components/PercentageForm.tsx +++ b/src/components/PercentageForm.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef} from 'react'; -import React, {useCallback, useMemo, useRef} from 'react'; +import React, {forwardRef, useCallback, useMemo, useRef} from 'react'; import useLocalize from '@hooks/useLocalize'; import {replaceAllDigits, stripCommaFromAmount, stripSpacesFromAmount, validatePercentage} from '@libs/MoneyRequestUtils'; import CONST from '@src/CONST'; @@ -18,12 +18,9 @@ type PercentageFormProps = { /** Custom label for the TextInput */ label?: string; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; -function PercentageForm({value: amount, errorText, onInputChange, label, ref, ...rest}: PercentageFormProps) { +function PercentageForm({value: amount, errorText, onInputChange, label, ...rest}: PercentageFormProps, forwardedRef: ForwardedRef) { const {toLocaleDigit, numberFormat} = useLocalize(); const textInput = useRef(null); @@ -59,14 +56,14 @@ function PercentageForm({value: amount, errorText, onInputChange, label, ref, .. value={formattedAmount} onChangeText={setNewAmount} placeholder={numberFormat(0)} - ref={(newRef: BaseTextInputRef | null) => { - if (typeof ref === 'function') { - ref(newRef); - } else if (ref && 'current' in ref) { + ref={(ref: BaseTextInputRef | null) => { + if (typeof forwardedRef === 'function') { + forwardedRef(ref); + } else if (forwardedRef && 'current' in forwardedRef) { // eslint-disable-next-line no-param-reassign - ref.current = newRef; + forwardedRef.current = ref; } - textInput.current = newRef; + textInput.current = ref; }} suffixCharacter="%" keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD} @@ -81,4 +78,4 @@ function PercentageForm({value: amount, errorText, onInputChange, label, ref, .. PercentageForm.displayName = 'PercentageForm'; -export default PercentageForm; +export default forwardRef(PercentageForm); diff --git a/src/components/RadioButtons.tsx b/src/components/RadioButtons.tsx index afbc8879c68c6..07e8fe38f772c 100644 --- a/src/components/RadioButtons.tsx +++ b/src/components/RadioButtons.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react'; +import React, {forwardRef, useEffect, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; @@ -33,12 +33,9 @@ type RadioButtonsProps = { /** The checked value, if you're using this component as a controlled input. */ value?: string; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; -function RadioButtons({items, onPress, defaultCheckedValue = '', radioButtonStyle, errorText, onInputChange = () => {}, value, ref}: RadioButtonsProps) { +function RadioButtons({items, onPress, defaultCheckedValue = '', radioButtonStyle, errorText, onInputChange = () => {}, value}: RadioButtonsProps, ref: ForwardedRef) { const styles = useThemeStyles(); const [checkedValue, setCheckedValue] = useState(defaultCheckedValue); @@ -77,4 +74,4 @@ function RadioButtons({items, onPress, defaultCheckedValue = '', radioButtonStyl RadioButtons.displayName = 'RadioButtons'; export type {Choice}; -export default RadioButtons; +export default forwardRef(RadioButtons); diff --git a/src/components/Share/ShareTabParticipantsSelector.tsx b/src/components/Share/ShareTabParticipantsSelector.tsx index 015576d5eb450..4d8bee4f9ccef 100644 --- a/src/components/Share/ShareTabParticipantsSelector.tsx +++ b/src/components/Share/ShareTabParticipantsSelector.tsx @@ -1,5 +1,4 @@ -import type {Ref} from 'react'; -import React from 'react'; +import React, {forwardRef} from 'react'; import {saveUnknownUserDetails} from '@libs/actions/Share'; import Navigation from '@libs/Navigation/Navigation'; import MoneyRequestParticipantsSelector from '@pages/iou/request/MoneyRequestParticipantsSelector'; @@ -9,14 +8,13 @@ import type ROUTES from '@src/ROUTES'; type ShareTabParticipantsSelectorProps = { detailsPageRouteObject: typeof ROUTES.SHARE_SUBMIT_DETAILS | typeof ROUTES.SHARE_DETAILS; - ref?: Ref; }; type InputFocusRef = { focus?: () => void; }; -function ShareTabParticipantsSelectorComponent({detailsPageRouteObject, ref}: ShareTabParticipantsSelectorProps) { +function ShareTabParticipantsSelectorComponent({detailsPageRouteObject}: ShareTabParticipantsSelectorProps, ref: React.Ref) { return ( (ShareTabParticipantsSelectorComponent); diff --git a/src/components/SingleChoiceQuestion.tsx b/src/components/SingleChoiceQuestion.tsx index a6e29661debd6..e520078504757 100644 --- a/src/components/SingleChoiceQuestion.tsx +++ b/src/components/SingleChoiceQuestion.tsx @@ -1,5 +1,5 @@ import type {ForwardedRef} from 'react'; -import React from 'react'; +import React, {forwardRef} from 'react'; // eslint-disable-next-line no-restricted-imports import type {Text as RNText} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -13,10 +13,9 @@ type SingleChoiceQuestionProps = { possibleAnswers: Choice[]; currentQuestionIndex: number; onInputChange: (value: string) => void; - ref?: ForwardedRef; }; -function SingleChoiceQuestion({prompt, errorText, possibleAnswers, currentQuestionIndex, onInputChange, ref}: SingleChoiceQuestionProps) { +function SingleChoiceQuestion({prompt, errorText, possibleAnswers, currentQuestionIndex, onInputChange}: SingleChoiceQuestionProps, ref: ForwardedRef) { const styles = useThemeStyles(); return ( @@ -39,4 +38,4 @@ function SingleChoiceQuestion({prompt, errorText, possibleAnswers, currentQuesti SingleChoiceQuestion.displayName = 'SingleChoiceQuestion'; -export default SingleChoiceQuestion; +export default forwardRef(SingleChoiceQuestion); diff --git a/src/components/StateSelector.tsx b/src/components/StateSelector.tsx index b32a63523aac0..e2be9281d0bb2 100644 --- a/src/components/StateSelector.tsx +++ b/src/components/StateSelector.tsx @@ -35,12 +35,12 @@ type StateSelectorProps = { /** object to get route details from */ stateSelectorRoute?: typeof ROUTES.SETTINGS_ADDRESS_STATE | typeof ROUTES.MONEY_REQUEST_STATE_SELECTOR; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; -function StateSelector({errorText, onBlur, value: stateCode, label, onInputChange, wrapperStyle, stateSelectorRoute = ROUTES.SETTINGS_ADDRESS_STATE, ref}: StateSelectorProps) { +function StateSelector( + {errorText, onBlur, value: stateCode, label, onInputChange, wrapperStyle, stateSelectorRoute = ROUTES.SETTINGS_ADDRESS_STATE}: StateSelectorProps, + ref: ForwardedRef, +) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {state: stateFromUrl} = useGeographicalStateAndCountryFromRoute(); @@ -100,6 +100,6 @@ function StateSelector({errorText, onBlur, value: stateCode, label, onInputChang StateSelector.displayName = 'StateSelector'; -export default StateSelector; +export default React.forwardRef(StateSelector); export type {State}; diff --git a/src/components/TextLink.tsx b/src/components/TextLink.tsx index 9dcb790a536e4..715d25e040a94 100644 --- a/src/components/TextLink.tsx +++ b/src/components/TextLink.tsx @@ -1,7 +1,7 @@ -import type {KeyboardEvent, KeyboardEventHandler, MouseEventHandler} from 'react'; -import React from 'react'; +import type {ForwardedRef, KeyboardEvent, KeyboardEventHandler, MouseEventHandler} from 'react'; +import React, {forwardRef} from 'react'; // eslint-disable-next-line no-restricted-imports -import type {GestureResponderEvent, StyleProp, TextStyle} from 'react-native'; +import type {GestureResponderEvent, Text as RNText, StyleProp, TextStyle} from 'react-native'; import useEnvironment from '@hooks/useEnvironment'; import useThemeStyles from '@hooks/useThemeStyles'; import {openLink as openLinkUtil} from '@userActions/Link'; @@ -32,7 +32,7 @@ type TextLinkProps = (LinkProps | PressProps) & onMouseDown?: MouseEventHandler; }; -function TextLink({href, onPress, children, style, onMouseDown = (event) => event.preventDefault(), ref, ...rest}: TextLinkProps) { +function TextLink({href, onPress, children, style, onMouseDown = (event) => event.preventDefault(), ...rest}: TextLinkProps, ref: ForwardedRef) { const {environmentURL} = useEnvironment(); const styles = useThemeStyles(); @@ -81,4 +81,4 @@ TextLink.displayName = 'TextLink'; export type {LinkProps, PressProps, TextLinkProps}; -export default TextLink; +export default forwardRef(TextLink); diff --git a/src/components/TimeModalPicker.tsx b/src/components/TimeModalPicker.tsx index fdd399b012006..be85fb7198391 100644 --- a/src/components/TimeModalPicker.tsx +++ b/src/components/TimeModalPicker.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {forwardRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -22,12 +22,9 @@ type TimeModalPickerProps = { /** Label for the picker */ label: string; - - /** Reference to the outer element */ - ref?: ForwardedRef; }; -function TimeModalPicker({value, errorText, label, onInputChange = () => {}, ref}: TimeModalPickerProps) { +function TimeModalPicker({value, errorText, label, onInputChange = () => {}}: TimeModalPickerProps, ref: ForwardedRef) { const styles = useThemeStyles(); const [isPickerVisible, setIsPickerVisible] = useState(false); const currentTime = value ? DateUtils.extractTime12Hour(value) : undefined; @@ -84,4 +81,4 @@ function TimeModalPicker({value, errorText, label, onInputChange = () => {}, ref } TimeModalPicker.displayName = 'TimeModalPicker'; -export default TimeModalPicker; +export default forwardRef(TimeModalPicker); diff --git a/src/components/withToggleVisibilityView.tsx b/src/components/withToggleVisibilityView.tsx index f9703eb34d246..809f2898aaa7a 100644 --- a/src/components/withToggleVisibilityView.tsx +++ b/src/components/withToggleVisibilityView.tsx @@ -1,4 +1,4 @@ -import type {ComponentType, ReactElement} from 'react'; +import type {ComponentType, ForwardedRef, ReactElement, RefAttributes} from 'react'; import React from 'react'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -9,8 +9,10 @@ type WithToggleVisibilityViewProps = { isVisible?: boolean; }; -export default function withToggleVisibilityView(WrappedComponent: ComponentType): (props: TProps & WithToggleVisibilityViewProps) => ReactElement | null { - function WithToggleVisibilityView({isVisible = false, ...rest}: WithToggleVisibilityViewProps) { +export default function withToggleVisibilityView( + WrappedComponent: ComponentType>, +): (props: TProps & WithToggleVisibilityViewProps & RefAttributes) => ReactElement | null { + function WithToggleVisibilityView({isVisible = false, ...rest}: WithToggleVisibilityViewProps, ref: ForwardedRef) { const styles = useThemeStyles(); return ( (WrappedComponent: Compo @@ -27,7 +30,7 @@ export default function withToggleVisibilityView(WrappedComponent: Compo } WithToggleVisibilityView.displayName = `WithToggleVisibilityViewWithRef(${getComponentDisplayName(WrappedComponent)})`; - return WithToggleVisibilityView; + return React.forwardRef(WithToggleVisibilityView); } export type {WithToggleVisibilityViewProps}; diff --git a/src/components/withViewportOffsetTop.tsx b/src/components/withViewportOffsetTop.tsx index 51e56d447c0fb..d3e9b63ad3eec 100644 --- a/src/components/withViewportOffsetTop.tsx +++ b/src/components/withViewportOffsetTop.tsx @@ -1,5 +1,5 @@ -import type {ComponentType} from 'react'; -import React, {useEffect, useState} from 'react'; +import type {ComponentType, ForwardedRef, RefAttributes} from 'react'; +import React, {forwardRef, useEffect, useState} from 'react'; import getComponentDisplayName from '@libs/getComponentDisplayName'; import addViewportResizeListener from '@libs/VisualViewport'; @@ -9,8 +9,8 @@ type ViewportOffsetTopProps = { viewportOffsetTop: number; }; -export default function withViewportOffsetTop(WrappedComponent: ComponentType) { - function WithViewportOffsetTop(props: Omit) { +export default function withViewportOffsetTop(WrappedComponent: ComponentType>) { + function WithViewportOffsetTop(props: Omit, ref: ForwardedRef) { const [viewportOffsetTop, setViewportOffsetTop] = useState(0); useEffect(() => { @@ -30,6 +30,7 @@ export default function withViewportOffsetTop ); @@ -37,5 +38,5 @@ export default function withViewportOffsetTop