From 0b55bfe3158869695da143e55fe80e2fa432a9c6 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 20 Dec 2023 13:38:41 +0100 Subject: [PATCH 1/7] [TS migration] Migrate 'StatePicker' component --- src/components/MenuItem.tsx | 8 +-- ...electorModal.js => StateSelectorModal.tsx} | 44 +++++++-------- .../StatePicker/{index.js => index.tsx} | 53 +++++-------------- src/libs/searchCountryOptions.ts | 1 + 4 files changed, 38 insertions(+), 68 deletions(-) rename src/components/StatePicker/{StateSelectorModal.js => StateSelectorModal.tsx} (72%) rename src/components/StatePicker/{index.js => index.tsx} (58%) diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index c2cc4abce6c5..c1d1aa7d71d2 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -84,7 +84,7 @@ type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & titleStyle?: ViewStyle; /** Any adjustments to style when menu item is hovered or pressed */ - hoverAndPressStyle: StyleProp>; + hoverAndPressStyle?: StyleProp>; /** Additional styles to style the description text below the title */ descriptionTextStyle?: StyleProp; @@ -174,7 +174,7 @@ type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & isSelected?: boolean; /** Prop to identify if we should load avatars vertically instead of diagonally */ - shouldStackHorizontally: boolean; + shouldStackHorizontally?: boolean; /** Prop to represent the size of the avatar images to be shown */ avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE]; @@ -219,10 +219,10 @@ type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & furtherDetails?: string; /** The function that should be called when this component is LongPressed or right-clicked. */ - onSecondaryInteraction: () => void; + onSecondaryInteraction?: () => void; /** Array of objects that map display names to their corresponding tooltip */ - titleWithTooltips: DisplayNameWithTooltip[]; + titleWithTooltips?: DisplayNameWithTooltip[]; }; function MenuItem( diff --git a/src/components/StatePicker/StateSelectorModal.js b/src/components/StatePicker/StateSelectorModal.tsx similarity index 72% rename from src/components/StatePicker/StateSelectorModal.js rename to src/components/StatePicker/StateSelectorModal.tsx index 003211478529..946c54048d79 100644 --- a/src/components/StatePicker/StateSelectorModal.js +++ b/src/components/StatePicker/StateSelectorModal.tsx @@ -1,48 +1,41 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; -import PropTypes from 'prop-types'; import React, {useEffect, useMemo} from 'react'; -import _ from 'underscore'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Modal from '@components/Modal'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import searchCountryOptions from '@libs/searchCountryOptions'; +import searchCountryOptions, {type CountryData} from '@libs/searchCountryOptions'; import StringUtils from '@libs/StringUtils'; import CONST from '@src/CONST'; -const propTypes = { +type State = keyof typeof COMMON_CONST.STATES; + +type StateSelectorModalProps = { /** Whether the modal is visible */ - isVisible: PropTypes.bool.isRequired, + isVisible: boolean; /** State value selected */ - currentState: PropTypes.string, + currentState?: State | ''; /** Function to call when the user selects a State */ - onStateSelected: PropTypes.func, + onStateSelected?: (state: CountryData) => void; /** Function to call when the user closes the State modal */ - onClose: PropTypes.func, + onClose?: () => void; /** The search value from the selection list */ - searchValue: PropTypes.string.isRequired, + searchValue: string; /** Function to call when the user types in the search input */ - setSearchValue: PropTypes.func.isRequired, + setSearchValue: (value: string) => void; /** Label to display on field */ - label: PropTypes.string, -}; - -const defaultProps = { - currentState: '', - onClose: () => {}, - onStateSelected: () => {}, - label: undefined, + label?: string; }; -function StateSelectorModal({currentState, isVisible, onClose, onStateSelected, searchValue, setSearchValue, label}) { +function StateSelectorModal({currentState = '', isVisible, onClose = () => {}, onStateSelected = () => {}, searchValue, setSearchValue, label}: StateSelectorModalProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -53,11 +46,11 @@ function StateSelectorModal({currentState, isVisible, onClose, onStateSelected, setSearchValue(''); }, [isVisible, setSearchValue]); - const countryStates = useMemo( + const countryStates: CountryData[] = useMemo( () => - _.map(_.keys(COMMON_CONST.STATES), (state) => { - const stateName = translate(`allStates.${state}.stateName`); - const stateISO = translate(`allStates.${state}.stateISO`); + Object.keys(COMMON_CONST.STATES).map((state) => { + const stateName = translate(`allStates.${state as State}.stateName`); + const stateISO = translate(`allStates.${state as State}.stateISO`); return { value: stateISO, keyForList: stateISO, @@ -81,6 +74,7 @@ function StateSelectorModal({currentState, isVisible, onClose, onStateSelected, hideModalContentWhileAnimating useNativeDriver > + {/* @ts-expect-error TODO: Remove this once ScreenWrapper (https://github.com/Expensify/App/issues/25128) is migrated to TypeScript. */} void; /** Label to display on field */ - label: PropTypes.string, -}; - -const defaultProps = { - value: undefined, - forwardedRef: undefined, - errorText: '', - onInputChange: () => {}, - label: undefined, + label?: string; }; -function StatePicker({value, errorText, onInputChange, forwardedRef, label}) { +function StatePicker({value, onInputChange, label, errorText = ''}: StatePickerProps, ref: ForwardedRef) { const styles = useThemeStyles(); const {translate} = useLocalize(); const [isPickerVisible, setIsPickerVisible] = useState(false); @@ -49,20 +36,20 @@ function StatePicker({value, errorText, onInputChange, forwardedRef, label}) { setIsPickerVisible(false); }; - const updateStateInput = (state) => { + const updateStateInput = (state: CountryData) => { if (state.value !== value) { - onInputChange(state.value); + onInputChange?.(state.value); } hidePickerModal(); }; - const title = value && _.keys(COMMON_CONST.STATES).includes(value) ? translate(`allStates.${value}.stateName`) : ''; + const title = value && Object.keys(COMMON_CONST.STATES).includes(value) ? translate(`allStates.${value}.stateName`) : ''; const descStyle = title.length === 0 ? styles.textNormal : null; return ( ( - -)); - -StatePickerWithRef.displayName = 'StatePickerWithRef'; - -export default StatePickerWithRef; +export default React.forwardRef(StatePicker); diff --git a/src/libs/searchCountryOptions.ts b/src/libs/searchCountryOptions.ts index 8fb1cc9c37f3..1fc5d343f556 100644 --- a/src/libs/searchCountryOptions.ts +++ b/src/libs/searchCountryOptions.ts @@ -37,3 +37,4 @@ function searchCountryOptions(searchValue: string, countriesData: CountryData[]) } export default searchCountryOptions; +export type {CountryData}; From 6c9c06f3740382285a9c4acee716eaea943c537e Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 20 Dec 2023 14:11:17 +0100 Subject: [PATCH 2/7] Use nullish coalescing operator --- src/components/StatePicker/StateSelectorModal.tsx | 4 ++-- src/components/StatePicker/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/StatePicker/StateSelectorModal.tsx b/src/components/StatePicker/StateSelectorModal.tsx index 946c54048d79..deee159ff906 100644 --- a/src/components/StatePicker/StateSelectorModal.tsx +++ b/src/components/StatePicker/StateSelectorModal.tsx @@ -82,14 +82,14 @@ function StateSelectorModal({currentState = '', isVisible, onClose = () => {}, o testID={StateSelectorModal.displayName} > From 9f5666ebfb3f3534feb9c3660122109507f61d0f Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 20 Dec 2023 16:10:23 +0100 Subject: [PATCH 3/7] Minor code improvement --- src/components/StatePicker/StateSelectorModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/StatePicker/StateSelectorModal.tsx b/src/components/StatePicker/StateSelectorModal.tsx index deee159ff906..2871c2ebdaf5 100644 --- a/src/components/StatePicker/StateSelectorModal.tsx +++ b/src/components/StatePicker/StateSelectorModal.tsx @@ -17,7 +17,7 @@ type StateSelectorModalProps = { isVisible: boolean; /** State value selected */ - currentState?: State | ''; + currentState?: State; /** Function to call when the user selects a State */ onStateSelected?: (state: CountryData) => void; @@ -35,7 +35,7 @@ type StateSelectorModalProps = { label?: string; }; -function StateSelectorModal({currentState = '', isVisible, onClose = () => {}, onStateSelected = () => {}, searchValue, setSearchValue, label}: StateSelectorModalProps) { +function StateSelectorModal({currentState, isVisible, onClose = () => {}, onStateSelected = () => {}, searchValue, setSearchValue, label}: StateSelectorModalProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); From 43a77afd47466250a642b479358a2622e5266a69 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Thu, 4 Jan 2024 10:17:52 +0100 Subject: [PATCH 4/7] Fix lint errors --- src/components/StatePicker/StateSelectorModal.tsx | 4 ++-- src/components/StatePicker/index.tsx | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/StatePicker/StateSelectorModal.tsx b/src/components/StatePicker/StateSelectorModal.tsx index 2871c2ebdaf5..5be88a77f887 100644 --- a/src/components/StatePicker/StateSelectorModal.tsx +++ b/src/components/StatePicker/StateSelectorModal.tsx @@ -6,7 +6,8 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import searchCountryOptions, {type CountryData} from '@libs/searchCountryOptions'; +import searchCountryOptions from '@libs/searchCountryOptions'; +import type {CountryData} from '@libs/searchCountryOptions'; import StringUtils from '@libs/StringUtils'; import CONST from '@src/CONST'; @@ -74,7 +75,6 @@ function StateSelectorModal({currentState, isVisible, onClose = () => {}, onStat hideModalContentWhileAnimating useNativeDriver > - {/* @ts-expect-error TODO: Remove this once ScreenWrapper (https://github.com/Expensify/App/issues/25128) is migrated to TypeScript. */} Date: Wed, 17 Jan 2024 09:10:07 +0100 Subject: [PATCH 5/7] Use logical or since label can be an empty string --- src/components/StatePicker/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/StatePicker/index.tsx b/src/components/StatePicker/index.tsx index 09f3b1a02802..a03e4f15fba0 100644 --- a/src/components/StatePicker/index.tsx +++ b/src/components/StatePicker/index.tsx @@ -62,7 +62,9 @@ function StatePicker({value, onInputChange, label, onBlur, errorText = ''}: Stat ref={ref} shouldShowRightIcon title={title} - description={label ?? translate('common.state')} + // Label can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + description={label || translate('common.state')} descriptionTextStyle={descStyle} onPress={showPickerModal} /> From 2ff79c0afc5bf287b8ae9eeae04029bf0b910e6f Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 24 Jan 2024 10:10:34 +0100 Subject: [PATCH 6/7] Replace nullish coalescing with logical or --- src/components/StatePicker/StateSelectorModal.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/StatePicker/StateSelectorModal.tsx b/src/components/StatePicker/StateSelectorModal.tsx index 5be88a77f887..cc6f88617907 100644 --- a/src/components/StatePicker/StateSelectorModal.tsx +++ b/src/components/StatePicker/StateSelectorModal.tsx @@ -82,14 +82,18 @@ function StateSelectorModal({currentState, isVisible, onClose = () => {}, onStat testID={StateSelectorModal.displayName} > Date: Wed, 24 Jan 2024 11:30:59 +0100 Subject: [PATCH 7/7] Remove outdated TODO --- src/components/StatePicker/StateSelectorModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/StatePicker/StateSelectorModal.tsx b/src/components/StatePicker/StateSelectorModal.tsx index cc6f88617907..798d3be7a698 100644 --- a/src/components/StatePicker/StateSelectorModal.tsx +++ b/src/components/StatePicker/StateSelectorModal.tsx @@ -89,7 +89,6 @@ function StateSelectorModal({currentState, isVisible, onClose = () => {}, onStat onBackButtonPress={onClose} />