Reputation: 81
I'm trying to use ant design's input component inside react-phone-number-input, as explained in this example: https://catamphetamine.gitlab.io/react-phone-number-input/ (Custom <input/>
)
The main issue is that ant design's input is a CompoundedComponent and I believe it's causing some issues. Only when using ant design's input I'm getting the following error:
Unhandled Runtime Error
TypeError: element.hasAttribute is not a function
My code looks like this:
import PhoneInput from 'react-phone-number-input'
import Input from 'ant-design'
<Form.Item name="phone">
<PhoneInput inputComponent={Input} />
</Form.Item>
Is there any way I can maybe export only the Input component from the CompoundedComponent so it works with the react-phone-number-input
library?
Upvotes: 4
Views: 3968
Reputation: 2019
react-phone-number-input
require input field reference to be pass if you want to use custom Input component. Antd Input field is not forwarding reference to dom input element instead it keep it at its own component level and that creating above element.
We may use useImparativeHandle by creating custom component and pass dom input field reference instead of original AntD reference.
My custom component may look like this.
const MyInput = React.forwardRef((props , ref) => {
const inputRef = useRef(null);
useImperativeHandle(
ref,
() => {
return inputRef.current.input;
},
[]
);
return <Input {...props} ref={inputRef} />;
});
Below is how you can use
<PhoneInput
value={value}
onChange={onChange}
placeholder="Enter phone number"
className="ant-input"
defaultCountry="IN"
international={true}
limitMaxLength={true}
countries={['IN']}
inputComponent={MyInput}
{...props}
/>
For full working demo check out Demo
Upvotes: 4
Reputation: 224
To make it workable with antd I can suggest you to make your own component inside of your react app intead of using their. Let's call this component PhoneNumberInput.tsx.
You can use their functions to format and parse phone number input such as formatPhoneNumber, parsePhoneNumber, getCountries
, however take a look to the dependencies they use in their npm module. From my point of view it would be better to use directly libphonenumber-js.
import React from 'react'
import { Form, Button, Input, Select } from 'antd'
import { formatPhoneNumber, parsePhoneNumber, getCountries } from 'react-phone-number-input'
import { CountryCode } from 'libphonenumber-js/types'
export const PhoneNumberInput = () => {
const [form] = Form.useForm()
const countryOptions: { value: string, label: JSX.Element}[] = getCountries().map((ZZ) => {
return {
value: ZZ,
label: <span> <img src={`images/flags/${ZZ}.svg`} className='country-flag-icon'/> {ZZ}</span> ,
}
})
countryOptions.unshift({
value: 'unknown',
label: <span><img src='images/flags/unknown.svg' className='country-flag-icon'/></span>,
})
function numberInputChanged(phone: any){
const parsed = parsePhoneNumber(phone, form.getFieldValue('countryCode'))
form.setFieldsValue({
phoneNumber: parsed && formatPhoneNumber(parsed.number)||phone,
countryCode: (parsed && parsed.country) || 'unknown',
})
}
function selectCountry(ZZ: any){
form.setFieldValue('countyCode', ZZ)
const phoneNumber = form.getFieldValue('phoneNumber')
if(!phoneNumber) return
const parsed = parsePhoneNumber(phoneNumber, ZZ)
parsed && parsed.number && form.setFieldValue('phoneNumber', parsed && formatPhoneNumber(parsed.number))
}
function getPhoneNumber(phone: {countryCode: CountryCode | 'unknown', phoneNumber: string}){
if(phone.countryCode === 'unknown') console.log('not formatted',phone.phoneNumber)
else console.log('formatted:', parsePhoneNumber(phone.phoneNumber, phone.countryCode)?.number)
}
return (
<>
<Button
form="test-phone-form"
htmlType="submit"
>
finish
</Button>
<Form
id="test-phone-form"
form={form}
onFinish={getPhoneNumber}
>
<Form.Item name={"phoneNumber"} >
<Input
placeholder="Your phone number"
onChange={(e) => numberInputChanged(e.target.value)}
addonBefore={
<Form.Item name={"countryCode"} style={{width: 100}}>
<Select showSearch options={countryOptions} onChange={(e) => selectCountry(e)}/>
</Form.Item>
}
/>
</Form.Item>
</Form>
</>
)
}
export default PhoneNumberInput
Then just import this component wherever you want to use it and call it: <PhoneNumberInput/>
P.S. here is a link to npm module with flags they used, however you might check their git to get only svg files and use them as images in your project
UPDATE example showed before, stores phone number as an object. To save only a string and keep it workable with antd forms you might be interested in a following example.
This component allows you to use it so simply as others antd's user inputs in Form.Item
import React, {useEffect, useState} from 'react'
import { Input, Select } from 'antd'
import {
parsePhoneNumber,
getCountries,
formatPhoneNumberIntl,
} from 'react-phone-number-input'
import { CountryCode } from 'libphonenumber-js'
import { useTranslation } from 'react-i18next'
type PhoneNumberFormItemProps = {
value?: any
onChange?: (value: any) => void
}
export const PhoneNumberInput: React.FC<PhoneNumberFormItemProps> = ({value, onChange}) => {
const { t } = useTranslation('single_components')
const [countryCode, setCountryCode] = useState<CountryCode>()
useEffect(() => {
if(value){
let parsed = parsePhoneNumber(value)
setCountryCode(parsed?.country)
}
}, [value])
const countryOptions: { value: string; label: React.ReactElement }[] =
getCountries().map((code) => {
return {
value: code,
label: (
<span className="phone-icon-container">
{' '}
<img src={`images/flags/${code}.svg`} className="country-flag-icon" /> {code}
</span>
),
}
})
function numberInputChanged(phone: string) {
let parsed = parsePhoneNumber(phone, countryCode)
//setCountryCode(parsed?.country) //useless if there is useEffect
if(typeof onChange === 'function') onChange(parsed ? formatPhoneNumberIntl(parsed.number) : phone)
}
function selectCountry(code: any) {
setCountryCode(code)
let parsed = parsePhoneNumber(value, code)
if(typeof onChange === 'function') onChange(parsed && formatPhoneNumberIntl(parsed.number))
}
return (
<Input
className="phone-input-container"
placeholder={t('User\'s phone number')}
onChange={(e) => numberInputChanged(e.target.value)}
value={value}
addonBefore={
<Select
showSearch
options={countryOptions}
onSelect={selectCountry}
// value={parsePhoneNumber(value)?.country} //if value === null => crash
value={countryCode}
placeholder={
<img src="images/flags/unknown.svg" className="unknown-country-flag-icon" />
}
className="same-as-input phone-country-select"
/>
}
/>
)
}
export default PhoneNumberInput
Upvotes: 1