react-phone-input-2 icon indicating copy to clipboard operation
react-phone-input-2 copied to clipboard

Pass ref to input

Open raphaelnoguier opened this issue 4 years ago • 17 comments

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 :)

raphaelnoguier avatar Mar 10 '20 10:03 raphaelnoguier

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.

bl00mber avatar Mar 11 '20 11:03 bl00mber

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.

jerocosio avatar May 15 '20 01:05 jerocosio

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=""
                            />

jerocosio avatar May 15 '20 01:05 jerocosio

@cosio55 were you able to catch the errors from the <PhoneInput /> using react-hook-form?? I'm kind of having a problem with that.

maxwaiyaki avatar Aug 22 '20 06:08 maxwaiyaki

@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.

MMauro94 avatar Nov 11 '20 12:11 MMauro94

@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',
    }
  }
}

sirwolfgang avatar Nov 26 '20 01:11 sirwolfgang

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

ntower avatar Nov 27 '20 17:11 ntower

Hello, @bl00mber! Please make possible integration with React Hook Form 🙏🙏🙏

xfo avatar Dec 22 '20 15:12 xfo

https://youssef-samih97.medium.com/how-to-use-react-phone-input-2-with-react-hook-form-bcad45e95f2d

youssefSamih avatar Jan 12 '21 04:01 youssefSamih

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 }}
            />
          );
        }}
      />
    );

xmd5a avatar Feb 05 '21 09:02 xmd5a

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>
  )
}

nicholasareed avatar Mar 16 '21 06:03 nicholasareed

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)}
            />
          );
        }}
      />

ravisankarchinnam avatar Jun 18 '21 10:06 ravisankarchinnam

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

AntonAL avatar Jun 25 '21 16:06 AntonAL

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)}
/>

isener avatar Oct 04 '21 22:10 isener

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

quannh25595 avatar Sep 29 '22 17:09 quannh25595

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}
 />

misbahkhalilaz avatar Nov 22 '23 20:11 misbahkhalilaz