fix(858): country combobox fix (#318)

keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
Mirlan 5 years ago committed by GitHub
parent 5050f12507
commit bec9a081f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 63
      src/features/Combobox/hooks/index.tsx
  2. 6
      src/features/Combobox/types.tsx
  3. 25
      src/features/FormStore/hooks/useFormState.tsx
  4. 32
      src/features/UserAccount/components/PagePersonalInfo/hooks/useUserInfo.tsx
  5. 52
      src/features/UserAccount/components/PagePersonalInfo/hooks/useUserInfoForm.tsx
  6. 8
      src/features/UserAccount/components/PagePersonalInfo/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 = <T extends Option>({ onChange, value }: Props<T>) => {
const [query, setQuery] = useState('')
const onQueryChange = useCallback(({ target }: ChangeEvent<HTMLInputElement>) => {
setQuery(target.value)
}, [])
return {
onQueryChange: onChange ?? onQueryChange,
query: value ?? query,
setQuery,
}
}
export const useCombobox = <T extends Option>(props: Props<T>) => {
const {
onSelect,
options,
} = props
export const useCombobox = <T extends Option>({
onBlur,
onChange,
onSelect,
options,
selected,
value: query,
}: Props<T>) => {
const inputFieldRef = useRef<HTMLInputElement>(null)
const [index, setIndex] = useState(0)
@ -54,12 +43,6 @@ export const useCombobox = <T extends Option>(props: Props<T>) => {
popoverRef,
} = useKeyboardScroll()
const {
onQueryChange,
query,
setQuery,
} = useQuery(props)
const {
close,
isOpen,
@ -67,11 +50,15 @@ export const useCombobox = <T extends Option>(props: Props<T>) => {
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 = <T extends Option>(props: Props<T>) => {
) || null
), [options])
const onQueryChange = (e: ChangeEvent<HTMLInputElement>) => {
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 = <T extends Option>(props: Props<T>) => {
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<HTMLInputElement>) => {
@ -128,14 +119,14 @@ export const useCombobox = <T extends Option>(props: Props<T>) => {
} 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 = <T extends Option>(props: Props<T>) => {
onOutsideClick,
onQueryChange,
open,
options: results,
options: filteredOptions,
popoverRef,
query,
toggle,

@ -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<T> = Pick<InputHTMLAttributes<HTMLInputElement>, (
| 'disabled'
| 'title'
| 'placeholder'
| 'onBlur'
)> & {
customListStyles?: CustomStyles,
error?: string | null,
@ -26,6 +25,7 @@ export type Props<T> = Pick<InputHTMLAttributes<HTMLInputElement>, (
onChange?: (event: ChangeEvent<HTMLInputElement>) => void,
onSelect?: (option: T | null) => void,
options: Array<T>,
value?: string,
selected?: boolean,
value: string,
withError?: boolean,
}

@ -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<FormState>({})
const [stateForSimilar, setStateForSimilar] = useState<SimilarState>(init)

@ -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<HTMLButtonElement>(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,
}
}

@ -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<HTMLInputElement>) => {
const email = trim(target.value)
if (email && !isValidEmail(email)) {
updateFormError(formIds.email, 'error_invalid_email_format')
}
}, [updateFormError])
const onPhoneBlur = useCallback(({ target }: ChangeEvent<HTMLInputElement>) => {
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<HTMLInputElement>) => {
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,
}
}

@ -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}
/>
<Combobox
value={readFormValue(country)}
value={selectedCountry?.name ?? readFormValue(country)}
labelLexic='form_country'
labelWidth={labelWidth}
onChange={updateFormValue(country)}
onBlur={onCountryBlur}
options={countries}
onSelect={onCountrySelect}
withError={false}
selected={Boolean(selectedCountry)}
/>
<Input
value={readFormValue(region)}

Loading…
Cancel
Save