react-phone-input-2
react-phone-input-2 copied to clipboard
Pass ref to input
Hello @bl00mber,
Is there a way to pass a ref to the input component ? I'm using react-hook-form, to handle form validation, but when i try to pass the ref like this :
<PhoneInput country="us" onlyCountries={['us', 'fr']} placeholder="Enter phone number" value={value} inputProps={{ ref: register({ required: 'This field is required', }), name }} />
I get lot of errors, because i think that i erase the initial ref so everything is broken.
Example : Cannot read property 'focus' of undefined at r.cursorToEnd
Cannot read property 'setSelectionRange' of undefined
Thanks :)
Yeah the component uses refs, you do not need to pass ref into it. If you need to pass ref, you probably doing something wrong. However I'm open to non-complicated suggestions.
Hi @raphaelnoguier do you found a way around it? I'm also using react-hook-form but haven't figured out how to pass the ref t the input.
I got it working using a Controller:
<Controller
as={(
<PhoneInput
country="mx"
preferredCountries={['mx', 'us']}
countryCodeEditable={false}
inputProps={{
name: 'phone_number',
required: true,
}}
// ref={() => { register({ required: true }); }}
name="phone_number"
enableSearch
localization={es}
searchPlaceholder="Buscar..."
containerClass="ease-linear duration-150 p-1 placeholder-gray-400 text-gray-700 bg-white rounded text-sm shadow focus:outline-none focus:shadow-outline w-full"
inputStyle={{ border: 'none' }}
buttonStyle={{ border: 'none' }}
buttonClass="rounded "
/>
)}
name="phone_number"
control={control}
defaultValue=""
/>
@cosio55 were you able to catch the errors from the <PhoneInput />
using react-hook-form?? I'm kind of having a problem with that.
@bl00mber I just started learning React, so correct me if I'm wrong, but couldn't you simply accept an inputRef
prop and forward it to the input
element? When undefined, you'd fallback to using the ref created inside your component.
I would actually need the input ref because I'd like to style the special label based on the focused state, and I can't think of a better way.
@cosio55's method didnt work for me, so I ended up doing it manually. I also tried adding forwardRef into a fork of the project and got the default flow of createRef working, but something about ref I was passing from my component wouldn't hook right.
This example uses React Form Hooks, React Bootstrap, and React Phone Input 2
export default function ActivateForm({ onSubmit, state }) {
const { register, handleSubmit, errors, formState, setValue } = useForm<Inputs>()
const phoneNumberRef = register('phone_number', validations.phone_number)
const handleApiSubmit = async (data, event) => {
console.log(data, event)
}
const handlePhoneNumberChange = (value, country, event, formattedValue) => {
setValue('phone_number', value, { shouldDirty: true, shouldValidate: formState.submitCount > 0 })
}
const phoneNumberValid = formState.submitCount > 0 && !errors.phone_number
const phoneNumberInvalid = formState.submitCount > 0 && !!errors.phone_number
const phoneButtonStyle = (() => {
if(phoneNumberValid) {
return styles.phone_number_button.valid
}
if(phoneNumberInvalid) {
return styles.phone_number_button.invalid
}
})
const phoneInputClass = (() => {
if(phoneNumberValid) {
return 'is-valid'
}
if(phoneNumberInvalid) {
return 'is-invalid'
}
})
return (
<Form onSubmit={handleSubmit(handleApiSubmit)}>
<Row>
<Form.Group as={Col}>
<Form.Label>Cell Phone</Form.Label>
<PhoneInput
specialLabel=''
onChange={handlePhoneNumberChange}
country={'us'}
buttonStyle={phoneButtonStyle()}
inputClass={phoneInputClass()}
inputProps={{
name: 'phone_number',
style: styles.phone_number,
}} />
<Form.Control.Feedback
style={{display: phoneNumberValid ? 'none' : 'inherit' }}
type="invalid"
className="text-center">{ errors.phone_number?.message }</Form.Control.Feedback>
</Form.Group>
</Row>
<Row>
<Col>
<Button
type="submit"
variant="primary"
className="float-right"
disabled={formState.isSubmitting} >Activate</Button>
</Col>
</Row>
</Form>
)
}
const validations = {
phone_number: {
required: { value: true, message: 'Cell Phone is required.' },
pattern: { value:/^(?:[0-9] ?){6,14}[0-9]$/, message: "Cell Phone is malformed." }, // https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s03.html
}
}
const styles = {
phone_number: {
fontSize: 'inherit',
letterSpacing: 'inherit',
height: 'inherit',
width: 'inherit',
},
phone_number_button: {
valid: {
borderTopColor: '#28a745',
borderLeftColor: '#28a745',
borderBottomColor: '#28a745',
},
invalid: {
borderTopColor: '#dc3545',
borderLeftColor: '#dc3545',
borderBottomColor: '#dc3545',
}
}
}
I also need a ref to the input (to be able to programmatically focus it in my case). It's already possible to pass a ref in via inputProps
, but that overwrites the internal ref (which causes the error in the original post). So instead, the internal ref needs to expect the possibility of a ref being passed in
It looks to me like a fairly small code change can accommodate this. Change lines 943-959 from this:
<input
className={inputClasses}
style={this.props.inputStyle}
onChange={this.handleInput}
onClick={this.handleInputClick}
onDoubleClick={this.handleDoubleClick}
onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur}
onCopy={this.handleInputCopy}
value={formattedNumber}
ref={el => this.numberInputRef = el}
onKeyDown={this.handleInputKeyDown}
placeholder={this.props.placeholder}
disabled={this.props.disabled}
type='tel'
{...this.props.inputProps}
/>
To this:
<input
className={inputClasses}
style={this.props.inputStyle}
onChange={this.handleInput}
onClick={this.handleInputClick}
onDoubleClick={this.handleDoubleClick}
onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur}
onCopy={this.handleInputCopy}
value={formattedNumber}
onKeyDown={this.handleInputKeyDown}
placeholder={this.props.placeholder}
disabled={this.props.disabled}
type='tel'
{...this.props.inputProps}
ref={el => {
this.numberInputRef = el;
if (typeof this.props.inputProps.ref === 'function') {
this.props.inputProps.ref(el);
} else if (typeof this.props.inputProps.ref === 'object') {
this.props.inputProps.ref.current = el;
}
}}
/>
I would try it out and make a merge request, but i'm on windows and thus blocked by https://github.com/bl00mber/react-phone-input-2/issues/346
Hello, @bl00mber! Please make possible integration with React Hook Form 🙏🙏🙏
https://youssef-samih97.medium.com/how-to-use-react-phone-input-2-with-react-hook-form-bcad45e95f2d
I resolved this issue using solution below: Simply used render props pattern and pass proper data:
<Controller
control={control}
defaultValue=""
name={id}
render={({ onChange }) => {
return (
<PhoneInput
country={country}
preferredCountries={preferredCountries}
onChange={(v) => onChange(v)}
inputProps={{ name: id }}
/>
);
}}
/>
);
You can also access the Input ref by assigning a ref to the PhoneInput component, and then getting ref.current.numberInputRef
like the following (so I can focus after clicking a button elsewhere):
const MyComponent = () => {
const ref = useRef();
const handleFocus = (num) => {
ref.current.numberInputRef.focus();
};
return (
<>
<PhoneInput
ref={ref}
/>
</>
<button onClick={handleFocus}>Focus</button>
)
}
If someone else is still struggling on integrating with react-hook-form, try this and will work like it should:
interface IPhoneNumberProps {
name: string;
value?: string;
placeholder?: string;
languageCode?: string;
isDisabled?: boolean;
isRequired?: boolean;
validations?: RegisterOptions;
}
<Controller
name={name}
control={control}
rules={validations}
defaultValue=""
render={({field}) => {
return (
<PhoneInput
{...field}
placeholder={placeholder}
inputProps={{
name,
disabled: isDisabled,
required: isRequired,
}}
country={languageCode?.toLowerCase() ?? 'us'}
containerClass="phone-number-format"
localization={localeCodes[`${languageCode?.toLowerCase()}`]}
onChange={(v) => field.onChange(v)}
/>
);
}}
/>
Please, make integration with Ant to be able to use this component as a custom input of AutoComplete
component.
https://ant.design/components/auto-complete/#components-auto-complete-demo-custom
I've ended up with following usage:
export const MyPhoneInput = React.forwardRef((props, ref) => {
const { value, onChange } = props
const inputRef = useRef()
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.numberInputRef.focus()
}
}))
return (
<ReactPhoneInput
ref={inputRef}
value={String(value)}
onChange={onChange}
/>
)
})
And usage as a custom input of AutoComplete
component:
import { AutoComplete } from 'antd'
const App = () => (
<AutoComplete>
<MyPhoneInput/>
</AutoComplete>
)
It solves an error "TypeError: inputRef.current.focus is not a function" when I'm trying to focus on input.
But, when I'm trying to type something, I'm getting following error:
TypeError: Cannot read property 'value' of undefined
I came with a small hacky solution that works with react-hook-form focus;
// ref(e) -> ref is coming from forwardRef(props, ref)
<PhoneInput
ref={phoneInputRef}
{...phoneInputProps}
inputProps={{
id,
ref: (e) => {
if (e && phoneInputRef.current) {
phoneInputRef.current.numberInputRef = e;
ref(e);
}
}
}}
enableSearch
country={'tr'}
containerClass={css(phoneInputStylesWeb)}
inputClass={globalStyles.input(!!props.value, hasError)}
/>
For those who still need correct behavior with ref
, please use it inside inputProps
const inputRef = useRef()
<PhoneInput
...
// point ref to correct
inputProps={{ ref: inputRef }}
....
/>
Due to this line https://github.com/bl00mber/react-phone-input-2/blob/39f787cf92b2ebb712b98cd8b62a3a7b38b5fde7/src/index.js#L978
This worked for me:
const { onChange, ...phoneInputProps } = register("phone", {
required: true,
});
<PhoneInput
country="us"
inputStyle={{ width: "100%" }}
value={getValues("phone")}
onChange={(e) => setValue("phone", e)}
inputProps={phoneInputProps}
/>