patw0929 / react-intl-tel-input

Rewrite International Telephone Input in React.js. (Looking for maintainers, and PRs & contributors are also welcomed!)
https://patw0929.github.io/react-intl-tel-input/
MIT License
284 stars 221 forks source link

input ref? #366

Open stoplion opened 3 years ago

stoplion commented 3 years ago

Is there a way to get a ref of the input?

goelshobhit commented 3 years ago

Import { useRef } from 'React';

const phoneNoRef = useRef();

return ( <IntlTelInput ref={phoneNoRef} ..... onSelectFlag={() => { const { current } = phoneNoRef; }} />

justin-calleja commented 3 years ago

I'm not sure if you can do what @goelshobhit suggests but I might be wrong. I have checked master branch for ref prop on IntlTelInput. No hits. The TelInput rendered by the main export of the package (IntlTelInput) does take a ref but I see no way of getting to it and if you could, you'd be overwriting what the package itself passes to it.

Here's what I'm doing (using v4.3.4 of react-intl-tel-input which has the same limitation):

TLDR; useRef on wrapper element and then on mount, query for input element and assign to a second ref you use just for storing the input.

const InputPhone = ({ autoFocus, field, onKeyDown, onPhoneNumberChange, onSelectFlag }: InputPhoneProps) => {
  // NOTE: all this getting of input el to add a blur event listener to it is to
  // remove the error / success message on blur of input (after user re-selects input after submit).
  // Submit (enter key down on input or press of button) will auto blur input without removing messages.
  // If this is not required, remove this code:
  const containerRef: MutableRefObject<null | HTMLDivElement> = useRef(null);
  const inputRef: MutableRefObject<null | HTMLInputElement> = useRef(null);
  useEffect(() => {
    const inputEl = containerRef.current?.getElementsByTagName('input')?.[0];
    if (!inputEl) {
      throw new Error(`Could not find the phone input element with name ${fieldName}`);
    }
    inputRef.current = inputEl;
    inputRef.current.addEventListener('blur', field.onBlur);

    return () => {
      if (inputRef.current && field.onBlur) {
        inputRef.current.removeEventListener('blur', field.onBlur);
      }
    };
  }, []);

  return (
    <div ref={containerRef}>
      <CPInputPhone
        autoFocus={autoFocus}
        fieldName={field.name ?? fieldName}
        onPhoneNumberChange={onPhoneNumberChange}
        onSelectFlag={onSelectFlag}
        // Pass onKeyDown to telInputProps because these get passed to <input> element
        // rendered by react-intl-tel-input.
        // onPhoneNumberChange does not get an event to work with. <input>'s onKeyDown does
        // so you can detect enter press:
        // NOTE: since react-intl-tel-input overwrites onChange and onBlur, passing them to
        // telInputProps is useless. onKeyDown is not overwritten.
        // https://github.com/patw0929/react-intl-tel-input/blob/v4.3.4/src/components/TelInput.js#L7-L22
        telInputProps={{ onKeyDown }}
      />
    </div>
  );
};

CPInputPhone is just a wrapper for IntlTelInput with some class names.

Edit:

Also note that if you're planning on adding some event listeners (like above) and you're working with useState... you're gonna have to wrap that in a useRef too otherwise you'll never get past the initial value of useState. See: https://stackoverflow.com/a/55265764/990159

const [error, setError] = useState(undefined)
field.onBlur = (e) => {
  // error will always be `undefined` here.
  if (error !== undefined) {
    setError(undefined);
  }
}

// Fix:
const [error, setError] = useState(undefined)
const errorRef = useRef(error);
const setErrorRef = (err) => {
    // keep new value of error after rerender:
    errorRef.current = err;
    // trigger rerender if necessary:
    setError(err);
};
field.onBlur = (e) => {
  if (errorRef.current !== undefined) {
    setErrorRef(undefined);
  }
}
andrewsantarin commented 3 years ago

@stoplion @goelshobhit @justin-calleja yes, there is one.

import React, { useEffect, useRef, useState } from 'react';
import IntlTelInput from 'react-intl-tel-input';

function App() {
  const intlTelInputRef = useRef<IntlTelInput | null>(null);

  useEffect(() => {
    intlTelInputRef.current?.tel?.focus();
  }, []);

  return (
    <div className="App">
      <IntlTelInput ref={intlTelInputRef} />
    </div>
  );
}

The intlTelInputRef has a property called tel which returns you the <input> HTML element. If you're looking for the TelInput React instance child, then I'm afraid that's not in the API yet.

brahimouindali commented 2 months ago

I'm using this package with react-hook-form Controller and I'm using the ref props like this

<Controller
                          control={control}
                          name="phone"
                          render={({ field }) => {
                            const { name, value, onChange, onBlur, ref } =
                              field;

                            return (
                              <IntlTelInput
                                ref={(props) => ref(props?.tel)}
                                fieldName={name}
                                telInputProps={{
                                  tabIndex: 1,
                                }}
                                placeholder="Phone"
                                containerClassName="intl-tel-input w-100"
                                fieldId="phone"
                                value={value}
                              />
                            );
                          }}
                          rules={{
                            required: "Please enter your phone number",
                          }}
                        />

but when I submit the form with empty value it doesn't focus on the input. any ideas?