From bec9a081f03019099ba33111689496a1fcacf0d4 Mon Sep 17 00:00:00 2001 From: Mirlan Date: Thu, 4 Mar 2021 18:18:18 +0600 Subject: [PATCH] fix(858): country combobox fix (#318) --- src/features/Combobox/hooks/index.tsx | 63 ++++++++----------- src/features/Combobox/types.tsx | 6 +- src/features/FormStore/hooks/useFormState.tsx | 25 +++----- .../PagePersonalInfo/hooks/useUserInfo.tsx | 32 ++++++---- .../hooks/useUserInfoForm.tsx | 52 +++++++-------- .../components/PagePersonalInfo/index.tsx | 8 ++- 6 files changed, 89 insertions(+), 97 deletions(-) diff --git a/src/features/Combobox/hooks/index.tsx b/src/features/Combobox/hooks/index.tsx index a74344be..6e14a4d1 100644 --- a/src/features/Combobox/hooks/index.tsx +++ b/src/features/Combobox/hooks/index.tsx @@ -9,6 +9,7 @@ import { useCallback, useRef, useEffect, + useMemo, } from 'react' import toLower from 'lodash/toLower' @@ -26,26 +27,14 @@ const isOptionClicked = (target: HTMLElement | null) => ( target?.getAttribute('role') === 'option' ) -const useQuery = ({ onChange, value }: Props) => { - const [query, setQuery] = useState('') - - const onQueryChange = useCallback(({ target }: ChangeEvent) => { - setQuery(target.value) - }, []) - - return { - onQueryChange: onChange ?? onQueryChange, - query: value ?? query, - setQuery, - } -} - -export const useCombobox = (props: Props) => { - const { - onSelect, - options, - } = props - +export const useCombobox = ({ + onBlur, + onChange, + onSelect, + options, + selected, + value: query, +}: Props) => { const inputFieldRef = useRef(null) const [index, setIndex] = useState(0) @@ -54,12 +43,6 @@ export const useCombobox = (props: Props) => { popoverRef, } = useKeyboardScroll() - const { - onQueryChange, - query, - setQuery, - } = useQuery(props) - const { close, isOpen, @@ -67,11 +50,15 @@ export const useCombobox = (props: Props) => { toggle, } = useToggle() - const results = matchSort( + const filteredOptions = useMemo(() => (selected ? options : matchSort( options, 'name', query, - ) + )), [ + options, + query, + selected, + ]) const findOptionByName = useCallback((optionName: string) => ( find( @@ -80,22 +67,25 @@ export const useCombobox = (props: Props) => { ) || null ), [options]) + const onQueryChange = (e: ChangeEvent) => { + onChange?.(e) + onSelect?.(null) + } + const onOptionSelect = useCallback((option: string, e?: BaseSyntheticEvent) => { e?.stopPropagation() const selectedOption = findOptionByName(option) - setQuery(selectedOption?.name || '') onSelect?.(selectedOption) close() }, [ close, findOptionByName, onSelect, - setQuery, ]) const onOutsideClick = (event: MouseEvent) => { if (event.target !== inputFieldRef.current) { - onOptionSelect(query) + close() } } @@ -106,16 +96,17 @@ export const useCombobox = (props: Props) => { if (isOptionClicked(target)) return onOptionSelect(query) + onBlur?.(event) } useEffect(() => { - const lastElementIndex = size(results) - 1 + const lastElementIndex = size(filteredOptions) - 1 if (index < 0) setIndex(0) if (index >= lastElementIndex) setIndex(lastElementIndex) }, [ index, options, - results, + filteredOptions, ]) const onKeyDown = useCallback((event: KeyboardEvent) => { @@ -128,14 +119,14 @@ export const useCombobox = (props: Props) => { } else if (event.key === 'ArrowDown') { setIndex(index + 1) } else if (event.key === 'Enter') { - onSelect?.(results[index]) + onSelect?.(filteredOptions[index]) close() inputFieldRef.current?.blur() } }, [ close, index, - results, + filteredOptions, onSelect, onKeyDownScroll, ]) @@ -151,7 +142,7 @@ export const useCombobox = (props: Props) => { onOutsideClick, onQueryChange, open, - options: results, + options: filteredOptions, popoverRef, query, toggle, diff --git a/src/features/Combobox/types.tsx b/src/features/Combobox/types.tsx index d0cb90c2..a074252c 100644 --- a/src/features/Combobox/types.tsx +++ b/src/features/Combobox/types.tsx @@ -1,13 +1,11 @@ import type { InputHTMLAttributes, ChangeEvent, - ReactNode, } from 'react' import type { CustomStyles } from 'features/Common' export type Option = { - children?: ReactNode, id: number, name: string, } @@ -16,6 +14,7 @@ export type Props = Pick, ( | 'disabled' | 'title' | 'placeholder' + | 'onBlur' )> & { customListStyles?: CustomStyles, error?: string | null, @@ -26,6 +25,7 @@ export type Props = Pick, ( onChange?: (event: ChangeEvent) => void, onSelect?: (option: T | null) => void, options: Array, - value?: string, + selected?: boolean, + value: string, withError?: boolean, } diff --git a/src/features/FormStore/hooks/useFormState.tsx b/src/features/FormStore/hooks/useFormState.tsx index a59f19c7..b2e28920 100644 --- a/src/features/FormStore/hooks/useFormState.tsx +++ b/src/features/FormStore/hooks/useFormState.tsx @@ -4,8 +4,6 @@ import { useCallback, useState } from 'react' import isObject from 'lodash/isObject' import isEqual from 'lodash/isEqual' import omit from 'lodash/omit' -import { useLexicsStore } from '../../LexicsStore' -import { Name } from '../../UserAccount/components/PagePersonalInfo/hooks/useUserInfoForm' type FieldState = { error: string | null, @@ -16,25 +14,22 @@ type FormState = {[formId: string]: FieldState} type SimilarState = {[formId: string]: string} export const useFormState = () => { - const { suffix } = useLexicsStore() let init = JSON.parse(localStorage.getItem('init') as string) - const country = init === null ? '' : init.country[`name_${suffix}` as Name] const countryID = init === null ? '' : init.country.id if (init !== null) { - init.address_line1 = init.address_line1 === null || undefined ? '' : init.address_line1 - init.address_line2 = init.address_line2 === null || undefined ? '' : init.address_line2 + init.address_line1 = init.address_line1 ?? '' + init.address_line2 = init.address_line2 ?? '' if (init.city_id !== undefined) { - init.city_id = init.city_id === null || undefined ? '' : init.city_id.toString() + init.city_id = init.city_id ?? '' } - init.city = init.city === null || undefined ? '' : init.city - init.country = country + init.city = init.city ?? '' init.countryId = `${countryID}` - init.cityId = '' - init.firstname = init.firstname === null || undefined ? '' : init.firstname - init.lastname = init.lastname === null || undefined ? '' : init.lastname - init.region = init.region === null || undefined ? '' : init.region - init.postalCode = 0 - init = omit(init, ['city_id', 'email', 'password', 'postal_code']) + init.cityId = init.city_id ?? '' + init.firstname = init.firstname ?? '' + init.lastname = init.lastname ?? '' + init.region = init.region ?? '' + init.postal_code = String(init.postal_code) ?? '' + init = omit(init, ['city_id', 'email', 'password', 'postalCode']) } const [formState, setFormState] = useState({}) const [stateForSimilar, setStateForSimilar] = useState(init) diff --git a/src/features/UserAccount/components/PagePersonalInfo/hooks/useUserInfo.tsx b/src/features/UserAccount/components/PagePersonalInfo/hooks/useUserInfo.tsx index a4fe0432..791abce7 100644 --- a/src/features/UserAccount/components/PagePersonalInfo/hooks/useUserInfo.tsx +++ b/src/features/UserAccount/components/PagePersonalInfo/hooks/useUserInfo.tsx @@ -11,30 +11,33 @@ import { formIds } from 'config/form' import { getUserInfo } from 'requests/getUserInfo' import { saveUserInfo } from 'requests/saveUserInfo' - import { City, getCountryCities } from 'requests/getCountryCities' + +import { useLexicsStore } from 'features/LexicsStore' + import type { SelectedCountry } from './useCountries' +import type { Name } from './useUserInfoForm' import { useUserInfoForm } from './useUserInfoForm' import { useValidateForm } from './useValidateForm' export const useUserInfo = () => { const validateForm = useValidateForm() const saveButton = useRef(null) + const { suffix } = useLexicsStore() const { cities, countries, - getCities, onCitySelect, + onCountryBlur, onCountrySelect, onPhoneBlur, onRegionOrCityChange, readFormError, readFormValue, - resetCities, - resetSelectedCity, selectedCity, - updateFormError, + selectedCountry, + setSelectedCountry, updateFormValue, } = useUserInfoForm() const readTrimmedValue = useCallback( @@ -86,7 +89,11 @@ export const useUserInfo = () => { const convertResponse = () => { forEach(response, (value: string | number | SelectedCountry, key: string) => { if (value && typeof value === 'object') { - onCountrySelect(value, false) + setSelectedCountry({ + ...value, + name: value[`name_${suffix}` as Name], + }) + updateFormValue(formIds.countryId)(String(value.id)) } else if (value) { updateFormValue(key)(String(value)) } @@ -106,24 +113,25 @@ export const useUserInfo = () => { localStorage.setItem('init', JSON.stringify(response)) convertResponse() }) - }, [updateFormValue, onCountrySelect]) + }, [ + suffix, + updateFormValue, + setSelectedCountry, + ]) return { cities, countries, - getCities, handleSubmit, onCitySelect, + onCountryBlur, onCountrySelect, onPhoneBlur, onRegionOrCityChange, readFormError, readFormValue, - resetCities, - resetSelectedCity, saveButton, - selectedCity, - updateFormError, + selectedCountry, updateFormValue, } } diff --git a/src/features/UserAccount/components/PagePersonalInfo/hooks/useUserInfoForm.tsx b/src/features/UserAccount/components/PagePersonalInfo/hooks/useUserInfoForm.tsx index 65bfabb3..91e40801 100644 --- a/src/features/UserAccount/components/PagePersonalInfo/hooks/useUserInfoForm.tsx +++ b/src/features/UserAccount/components/PagePersonalInfo/hooks/useUserInfoForm.tsx @@ -1,4 +1,4 @@ -import type { ChangeEvent, FocusEvent } from 'react' +import type { ChangeEvent } from 'react' import { useEffect, useCallback, @@ -6,14 +6,12 @@ import { import trim from 'lodash/trim' -import { isValidEmail } from 'helpers/isValidEmail' import { isValidPhone } from 'helpers/isValidPhone' import { formatPhoneCode } from 'helpers/formatPhoneCode' import { formIds } from 'config/form' import { useForm } from 'features/FormStore' -import { useLexicsStore } from 'features/LexicsStore' import type { SelectedCountry } from './useCountries' import { useCountries } from './useCountries' @@ -44,15 +42,6 @@ export const useUserInfoForm = () => { selectedCity, } = useCities() - const { suffix } = useLexicsStore() - - const onEmailBlur = useCallback(({ target }: FocusEvent) => { - const email = trim(target.value) - if (email && !isValidEmail(email)) { - updateFormError(formIds.email, 'error_invalid_email_format') - } - }, [updateFormError]) - const onPhoneBlur = useCallback(({ target }: ChangeEvent) => { const phone = target.value if (phone && !isValidPhone(phone)) { @@ -60,33 +49,40 @@ export const useUserInfoForm = () => { } }, [updateFormError]) - const onCountrySelect = useCallback(( - country: SelectedCountry, - updatePhoneCode: boolean = true, - ) => { + const phone = trim(readFormValue(formIds.phone)) + const onCountrySelect = useCallback((country: SelectedCountry) => { + if (country?.id === selectedCountry?.id) return + setSelectedCountry(country) - const countryName = country ? country[`name_${suffix}` as Name] : '' - updateFormValue(formIds.country)(countryName) + updateFormValue(formIds.countryId)(country?.id ? String(country?.id) : '') updateFormValue(formIds.cityId)('') - updateFormValue(formIds.countryId)(`${country?.id}`) resetCities() resetSelectedCity() - if (updatePhoneCode) { - const code = country?.phone_code - ? formatPhoneCode(country.phone_code) - : '' + const selectedCountryCode = formatPhoneCode(selectedCountry?.phone_code || '') + const hasPhoneNumber = ( + phone + && selectedCountryCode + && phone !== selectedCountryCode + ) + if (!hasPhoneNumber) { + const code = formatPhoneCode(country?.phone_code || '') updateFormValue(formIds.phone)(code) } }, [ + phone, + selectedCountry, resetCities, resetSelectedCity, setSelectedCountry, updateFormValue, - suffix, ]) + const onCountryBlur = () => { + updateFormValue(formIds.country)(selectedCountry?.name || '') + } + const onRegionOrCityChange = useCallback((fieldName: string) => ( ({ target }: ChangeEvent) => { if (selectedCountry) { @@ -115,18 +111,16 @@ export const useUserInfoForm = () => { return { cities, countries, - getCities, onCitySelect, + onCountryBlur, onCountrySelect, - onEmailBlur, onPhoneBlur, onRegionOrCityChange, readFormError, readFormValue, - resetCities, - resetSelectedCity, selectedCity, - updateFormError, + selectedCountry, + setSelectedCountry, updateFormValue, } } diff --git a/src/features/UserAccount/components/PagePersonalInfo/index.tsx b/src/features/UserAccount/components/PagePersonalInfo/index.tsx index 2a85169b..c5304f9f 100644 --- a/src/features/UserAccount/components/PagePersonalInfo/index.tsx +++ b/src/features/UserAccount/components/PagePersonalInfo/index.tsx @@ -11,7 +11,7 @@ import { useUserInfo } from './hooks/useUserInfo' import { SolidButton } from '../../styled' import { Form, ButtonWrapper } from './styled' -const labelWidth = 76 +const labelWidth = 104 const { address1, @@ -32,12 +32,14 @@ export const PagePersonalInfo = () => { countries, handleSubmit, onCitySelect, + onCountryBlur, onCountrySelect, onPhoneBlur, onRegionOrCityChange, readFormError, readFormValue, saveButton, + selectedCountry, updateFormValue, } = useUserInfo() @@ -91,13 +93,15 @@ export const PagePersonalInfo = () => { withError={false} />