Skip to content

Commit

Permalink
Merge pull request #33352 from VickyStash/ts-migration/statePicker-co…
Browse files Browse the repository at this point in the history
…mponent

[TS migration] Migrate 'StatePicker' component to TypeScript
  • Loading branch information
mountiny authored Jan 25, 2024
2 parents cccb89a + 7617114 commit b4acfb6
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -1,48 +1,42 @@
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 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();

Expand All @@ -53,11 +47,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,
Expand Down Expand Up @@ -88,12 +82,16 @@ function StateSelectorModal({currentState, isVisible, onClose, onStateSelected,
testID={StateSelectorModal.displayName}
>
<HeaderWithBackButton
// Label can be an empty string
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
title={label || translate('common.state')}
shouldShowBackButton
onBackButtonPress={onClose}
/>
<SelectionList
headerMessage={headerMessage}
// Label can be an empty string
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
textInputLabel={label || translate('common.state')}
textInputValue={searchValue}
sections={[{data: searchResults, indexOffset: 0}]}
Expand All @@ -108,8 +106,7 @@ function StateSelectorModal({currentState, isVisible, onClose, onStateSelected,
);
}

StateSelectorModal.propTypes = propTypes;
StateSelectorModal.defaultProps = defaultProps;
StateSelectorModal.displayName = 'StateSelectorModal';

export default StateSelectorModal;
export type {State};
Original file line number Diff line number Diff line change
@@ -1,45 +1,33 @@
import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST';
import PropTypes from 'prop-types';
import React, {useState} from 'react';
import type {ForwardedRef} from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import FormHelpMessage from '@components/FormHelpMessage';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import refPropTypes from '@components/refPropTypes';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import type {CountryData} from '@libs/searchCountryOptions';
import StateSelectorModal from './StateSelectorModal';
import type {State} from './StateSelectorModal';

const propTypes = {
type StatePickerProps = {
/** Error text to display */
errorText: PropTypes.string,
errorText?: string;

/** State to display */
value: PropTypes.string,
value?: State;

/** Callback to call when the input changes */
onInputChange: PropTypes.func,

/** A ref to forward to MenuItemWithTopDescription */
forwardedRef: refPropTypes,
onInputChange?: (value: string) => void;

/** Label to display on field */
label: PropTypes.string,
label?: string;

/** Callback to call when the picker modal is dismissed */
onBlur: PropTypes.func,
};

const defaultProps = {
value: undefined,
forwardedRef: undefined,
errorText: '',
onInputChange: () => {},
label: undefined,
onBlur: () => {},
onBlur?: () => void;
};

function StatePicker({value, errorText, onInputChange, forwardedRef, label, onBlur}) {
function StatePicker({value, onInputChange, label, onBlur, errorText = ''}: StatePickerProps, ref: ForwardedRef<View>) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [isPickerVisible, setIsPickerVisible] = useState(false);
Expand All @@ -51,29 +39,31 @@ function StatePicker({value, errorText, onInputChange, forwardedRef, label, onBl

const hidePickerModal = (shouldBlur = true) => {
if (shouldBlur) {
onBlur();
onBlur?.();
}
setIsPickerVisible(false);
};

const updateStateInput = (state) => {
const updateStateInput = (state: CountryData) => {
if (state.value !== value) {
onInputChange(state.value);
onInputChange?.(state.value);
}
// If the user selects any state, call the hidePickerModal function with shouldBlur = false
// to prevent the onBlur function from being called.
hidePickerModal(false);
};

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 (
<View>
<MenuItemWithTopDescription
ref={forwardedRef}
ref={ref}
shouldShowRightIcon
title={title}
// Label can be an empty string
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
description={label || translate('common.state')}
descriptionTextStyle={descStyle}
onPress={showPickerModal}
Expand All @@ -94,18 +84,6 @@ function StatePicker({value, errorText, onInputChange, forwardedRef, label, onBl
);
}

StatePicker.propTypes = propTypes;
StatePicker.defaultProps = defaultProps;
StatePicker.displayName = 'StatePicker';

const StatePickerWithRef = React.forwardRef((props, ref) => (
<StatePicker
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
forwardedRef={ref}
/>
));

StatePickerWithRef.displayName = 'StatePickerWithRef';

export default StatePickerWithRef;
export default React.forwardRef(StatePicker);
1 change: 1 addition & 0 deletions src/libs/searchCountryOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ function searchCountryOptions(searchValue: string, countriesData: CountryData[])
}

export default searchCountryOptions;
export type {CountryData};

0 comments on commit b4acfb6

Please sign in to comment.