catamphetamine/react-phone-number-input

(React Native) Can this package be used in the React Native?

irsyaadbp opened this issue · 70 comments

Can this package be used in the react native

Update: Yes, "without country select" component.

First, create PhoneTextInput.js file:

import React, { useCallback } from 'react'
import PropTypes from 'prop-types'
import { TextInput } from 'react-native'

function PhoneTextInput({ 
  placeholder, 
  autoComplete, 
  autoFocus, 
  value, 
  onChange 
}, ref) {
  // Instead of `onChangeText` it could use `onChange` and get `value` from `nativeEvent.text`.
  const onChangeText = useCallback((value) => {
    onChange({
      preventDefault() { this.defaultPrevented = true },
      target: { value }
    })
  }, [onChange])
  return (
    <TextInput
      ref={ref}
      placeholder={placeholder}
      autoFocus={autoFocus}
      autoCompleteType={autoComplete}
      keyboardType="phone-pad"
      onChangeText={onChangeText}
      value={value}/>
  )
}

PhoneTextInput = React.forwardRef(PhoneTextInput)

PhoneTextInput.propTypes = {
  placeholder: PropTypes.string,
  autoComplete: PropTypes.string,
  autoFocus: PropTypes.bool,
  value: PropTypes.string,
  onChange: PropTypes.func.isRequired
}

export default PhoneTextInput

Then, in a React Native application:

import React, { useState } from 'react'
import PhoneInput from 'react-phone-number-input/input'
import PhoneTextInput from './PhoneTextInput'

function Example() {
  const [value, setValue] = useState()
  return (
    <PhoneInput
      style={...}
      smartCaret={false}
      inputComponent={PhoneTextInput}
      defaultCountry="US"
      value={value}
      onChange={setValue} /> 
  )
}

Report in this issue if it works.

Also see the previous question: #283 (comment)

@catamphetamine "without country select" doesn't work with react native. Although I am trying out a simple example with text input and the libphonenumber-js to work out something. Till now what I have done has worked till some extent but it will be great if you could help me in understanding how the smart caret works in your react component. For now the caret has a jumping effect when it meets with a blank space or a bracket.

@whimsicaldreamer

"without country select" doesn't work with react native.

How are you using it?
Is it v3?

Till now what I have done has worked till some extent but it will be great if you could help me in understanding how the smart caret works in your react component. For now the caret has a jumping effect when it meets with a blank space or a bracket.

Smart caret is using input-format library.

@catamphetamine

How are you using it?

I am using it as per the instructions in the readme for using "without country select"

Is it v3?

Yes, the latest. 3.0.13.

Smart caret is using input-format library.

Any suggestions on how to use with react native input or how you implement it with react input?

I am using it as per the instructions in the readme for using "without country select"

Then it won't work because React Native most likely doesn't support <select/>.
https://github.com/catamphetamine/react-phone-number-input/blob/master/source/PhoneInput.js
See if inputComponent={Select} fixes the issue.

Any suggestions on how to use with react native input or how you implement it with react input?

See the InputSmart.js file of this library if you want to copy the behavior.

Then it won't work because React Native most likely doesn't support <select/>

Yes you are correct.

See if inputComponent={Select} fixes the issue

You mean to pass in the react native TextInput component here?

You mean to pass in the react native TextInput component here?

Yes.

Maybe something like:

import React from 'react'
import { TextInput } from 'react-native'

export default function PhoneTextInput({ 
  placeholder, 
  autoComplete, 
  autoFocus, 
  value, 
  onChange 
}) {
  return (
    <TextInput
      placeholder={placeholder}
      autoFocus={autoFocus}
      autoCompleteType={autoComplete}
      keyboardType="phone-pad"
      onChangeText={onChange}
      value={value}
    />
  );
}

Report if it works.

Ok, copying the above as a separate component and calling it as following:

<PhoneInput
      country="IN"
      inputComponent={ PhoneTextInput } // <= From your example component above
      value={this.state.mobile_number}
      onChange={ this._phoneInputHandler }
/>

Let me know if my interpretation is correct.

Yes, see if it works.
I don't have React Native so I didn't check.

@catamphetamine On Load I get a warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

On typing in the first digit, I also get an error: TypeError: Undefined is not an object(evaluating 'input.value')

On Load I get a warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef().

The component uses React.forwardRef().
I added ref={...} on demo page to "without country select" and it didn't print any warning.

On typing in the first digit, I also get an error:

input.value is being accessed from input-format package.
input is the ref.
ref isn't working in your case for some reason, so input is undefined in input-format package.

Actually, we didn't forward ref in <PhoneTextInput/>.

See if this works:

import React from 'react'
import PropTypes from 'prop-types'
import { TextInput } from 'react-native'

function PhoneTextInput({ 
  placeholder, 
  autoComplete, 
  autoFocus, 
  value, 
  onChange 
}, ref) {
  return (
    <TextInput
      ref={ref}
      placeholder={placeholder}
      autoFocus={autoFocus}
      autoCompleteType={autoComplete}
      keyboardType="phone-pad"
      onChangeText={onChange}
      value={value}
    />
  )
}

PhoneTextInput = React.forwardRef(PhoneTextInput)

PhoneTextInput.propTypes = {
  placeholder: PropTypes.string,
  autoComplete: PropTypes.string,
  autoFocus: PropTypes.bool,
  value: PropTypes.string,
  onChange: PropTypes.func.isRequired
}

export default PhoneTextInput

input.value is being accessed from input-format package.

Do I need to install the input-format package separately?? Since I still get the error: TypeError: Undefined is not an object(evaluating 'input.value'). Although the warning is gone.

Do I need to install the input-format package separately?

No, it's installed automatically.
The code above should work because it forwards ref now.
Did the forwardRef warning disappear?

Yes, the forward ref warning disappeared but I get the TypeError.

I guess input-format is only for react.

Screenshot_2020-01-23-22-09-05-727_com furry

So, the error originates at that function:
https://github.com/catamphetamine/input-format/blob/26abb77bec3ac5984f7fe99408846464fda6136a/source/input%20control.js#L35-L38
Called from here:
https://github.com/catamphetamine/input-format/blob/26abb77bec3ac5984f7fe99408846464fda6136a/source/react/Input.js#L35-L41
And input there is ref.current.
There, for some reason ref.current is undefined.

I read a bit about React Native, and found out that even if the input wasn't undefined, still there's no input.value property, so I guess input-format won't work in React Native.
The alternative then is passing smartCaret={false} property when creating a <PhoneInput/> "without country select" component element.

const InputComponent = smartCaret ? InputSmart : InputBasic

So, see if such code works:

import PhoneInput from 'react-phone-number-input/input`

<PhoneInput smartCaret={false} .../>

On adding smartCaret: { false } I receive a TypeError on keying in numbers.
This is what I have currently:

<PhoneInput
       style={ styles.phoneNumberInput }
       smartCaret={ false }
       country="IN"
       inputComponent={ PhoneTextInput }
       value={ this.state.mobile_number }
       onChange={ (number) => this._phoneInputHandler(number) }
/> 

Screenshot_2020-01-24-11-38-40-638_com furry

So event.target.value is undefined at this line of code:

let newValue = parseIncompletePhoneNumber(event.target.value)

You can add some kind of console.log(event.target.value) in onChange to see what's the value there.

<PhoneTextInput onChange={event => { console.log(event); onChange(event); }}/>

The log turns out to be huge! So I am pasting the nativeEvent right after I typed a single digit 9:

"nativeEvent": {"eventCount": 1, "target": 43, "text": "9"}, "target": 43, "timeStamp": 1579864644085, "type": undefined}

Huh, so target is a number in React Native?
How does one get value then.
Well, ok, then try this instead:

<PhoneTextInput onTextChange={value => onChange({
  preventDefault() { this.defaultPrevented = true },
  target: { value }
})}/>

It creates an event stub from a value.

Perhaps there're more correct ways.

I guess we get the values from nativeEvent.text.

<PhoneTextInput onTextChange={value => onChange({
  preventDefault() { this.defaultPrevented = true },
  target: { value }
})}/>

Where should I try the above?

@whimsicaldreamer

I guess we get the values from nativeEvent.text.

Maybe. That could be a second route. Not yet though.

Where should I try the above?

I meant:

<TextInput onTextChange={(value) => onChange({
  preventDefault() { this.defaultPrevented = true },
  target: { value }
})}/>

My current implementation was as following:

<PhoneInput
    style={ styles.phoneNumberInput }
    smartCaret={ false }
    country="IN"
    inputComponent={ PhoneTextInput }
    value={ this.state.mobile_number }
    onChange={ (number) => this._phoneInputHandler(number) }
/>

where PhoneTextInput is:

function PhoneTextInput({
        placeholder,
        autoComplete,
        autoFocus,
        value,
        onChange,
        style,
    }, ref) {
    return (
        <TextInput
            style={ style }
            ref={ ref }
            placeholder={ placeholder }
            autoFocus={ autoFocus }
            autoCompleteType={ autoComplete }
            keyboardType="phone-pad"
            onChange={ event => { console.log(event); onChange(event); } }
            value={ value }
            onTextChange={value => onChange({
                preventDefault() {
                    this.defaultPrevented = true
                },
                target: {value}
            })}
        />
    );
}

PhonePadInput = React.forwardRef(PhoneTextInput);

I am not sure if this is the way you mean.

On a side note, I have been trying out to implement this on my own without react-phone-number-input library and using only libphonenumber-js. I have been able to get the formatting done on the fly but one issue arises and that is on erasing a phone number entered. The issue is similar to this: catamphetamine/libphonenumber-js#225. If you would like to know how I am doing it, I would love to paste it in here.

Remove onChange={ event => { console.log(event); onChange(event); } } because it's now replaced with onChangeText.

If you would like to know how I am doing it, I would love to paste it in here.

I won't assist with any custom components, only with parts of react-phone-number-input.

Remove onChange={ event => { console.log(event); onChange(event); } } because it's now replaced with onChangeText.

function PhoneTextInput({
        placeholder,
        autoComplete,
        autoFocus,
        value,
        onChange,
        style,
    }, ref) {
    return (
        <TextInput
            style={ style }
            ref={ ref }
            placeholder={ placeholder }
            autoFocus={ autoFocus }
            autoCompleteType={ autoComplete }
            keyboardType="phone-pad"
            value={ value }
            onTextChange={value => onChange({
                preventDefault() {
                    this.defaultPrevented = true
                },
                target: {value}
            })}
        />
    );
}

PhoneTextInput = React.forwardRef(PhoneTextInput);

and then in the main screen I am calling it as follows:

_phoneInputHandler = (value) => {
      this.setState({ mobile_number: value });
};

<PhoneInput
      style={ styles.phoneNumberInput }
      smartCaret={ false }
      country="IN"
      inputComponent={ PhonePadInput }
      value={ this.state.mobile_number }
      onChange={ (number) => this._phoneInputHandler(number) }
/>

With the above in place, when I key in a number, it comes and then disappears.

What does console.log say?

_phoneInputHandler = (value) => {
      console.log(value)
      this.setState({ mobile_number: value });
};

It has nothing.

I won't assist with any custom components, only with parts of react-phone-number-input

Its completely fine. Actually I was hoping for some guidance on the way you took to solve that issue in your react-phone-number-input package.

It has nothing.

What do you mean.
What is the value there.

Actually I was hoping for some guidance on the way you took to solve that issue in your react-phone-number-input package.

If there're caret issues when clearing the number then see InputBasic.js 's onChange function fix.

What is the value there.

Nothing gets logged.

Nothing gets logged.

Nothing gets logged when you input a digit?

Exactly. Nothing gets logged when I input a digit.

Exactly. Nothing gets logged when I input a digit.

Then it's not working.
See what does it output in onTextChange.

          onTextChange={value => {
            console.log('onTextChange', value)
            onChange({
                preventDefault() {
                    this.defaultPrevented = true
                },
                target: {value}
            })
          }

Still nothing gets logged.

Still nothing gets logged.

Hmm, that would mean React Native's <TextInput/> onTextChange isn't working, which is unlikely.
You could also try something else, like replacing onTextChange with onChange(event) and then event.nativeEvent.text.

@whimsicaldreamer

Exactly. Nothing gets logged when I input a digit.

I looked at the source code again and turns out that instead of onTextChange property name it should have been onChangeText property name.
If you still have the code, see if it works with onTextChange property renamed to onChangeText property.

@catamphetamine for now, I don't have the code as this part was delaying my progress with my app. But as far as I remember I did spot his mistake and tried with onChangeText but it didn't work out.

BTW, just a thought, why don't you publish a new package for RN? It would be just great! ;)

@whimsicaldreamer

But as far as I remember I did spot his mistake and tried with onChangeText but it didn't work out.

I see.
Ok.

BTW, just a thought, why don't you publish a new package for RN?

Because I haven't worked with React Native, and so I'm not a specialist in it, and developing a React Native version of this package would better be done by someone being an expert in React Native.
I don't even have it on my PC.
That's why I didn't test the code myself.
Don't have time for everything in this world )

so I'm not a specialist in it

Neither am I. :(

Don't have time for everything in this world

True for everyone. :)

Not sure if I have the time for this, but I'll take a look, as I might need to use this in my RN project. @catamphetamine This would require me refactoring the styles to get rid of CSS to make it work in RN. Should I just fork into a separate library?

@nandorojo

This would require me refactoring the styles to get rid of CSS to make it work in RN. Should I just fork into a separate library?

I've posted the theoretically supposedly working React Native "without country select" component in the first comment in this issue.
See if it works.
It doesn't imply any styles, so no things to "get rid of".

Hey @catamphetamine, I can confirm that the snippets in your first comment work on React Native. I implemented my own country selector dropdown to go along with it using react-native-picker-select and it works great.

I couldn't figure out how to replicate the behaviour in the "Force international format" example though, where the country code is part of the input field. Maybe there should be an additional flag in the props for that? Since setting a country using the country prop always hides the country code in the input field, and countrySelectComponent doesn't seem to do anything at all.

@ianmartorell I see, so it works then. I guess I'll release it as a subpackage then.

I couldn't figure out how to replicate the behaviour in the "Force international format" example though, where the country code is part of the input field.

There currently is no such mode because I personally thought that it's a better UX when the country code is outside of the input field.
Maybe it's not always.
Whatever.
There could be a special inputCountryCallingCode: boolean property if the feature was implemented in some hypothetical future.

I ended up implementing it with the country code inside the picker, but I think it would've looked good too with the country code in the field and just a flag in the picker. Maybe it's better UX like you said, but I guess it's good to let the user choose.

Published react-phone-number-input@3.1.0 with the React Native component included under /react-native-input subpackage.

@ianmartorell

I ended up implementing it with the country code inside the picker, but I think it would've looked good too with the country code in the field and just a flag in the picker. Maybe it's better UX like you said, but I guess it's good to let the user choose.

Also added a new withCountryCallingCode: boolean property of the /input component that enables the type of feature you were describing.

We're using

import React, {useState, useEffect} from 'react';
import PhoneInput from 'react-phone-number-input/react-native-input';

import i18nGlobal from '../../utils/i18nGlobal';
const i18n = i18nGlobal.i18n;

function AuthScreen(props) {
  const [phone, setPhone] = useState();
  return (
       <PhoneInput
          placeholder={i18n.t('auth_screen.phone_input_text')}
          defaultCountry="US"
          value={phone}
          onChange={setPhone}
        />
  );
}

We're getting the error: Error: [libphonenumber-js] metadataargument was passed but it's not valid metadata. Must be an object having.countrieschild object property. Got a string: US.

@seamive See if react-phone-number-input@3.1.17 doesn't have the error.

@catamphetamine Hi! Could you add style and placeholderTextStyle props into PhoneInput component? It'd make styling much more painless

@vpodolyan All rest props should be passed through to the input component.
If something isn't passed through, then you should debug it in the code of the library.

Parent props aren't being passed to children, ...rest solve this
placeholder, autoFocus, value it's not necessary

This solves style and placeholderTextColor problems

https://gitlab.com/catamphetamine/react-phone-number-input/-/blob/master/source/react-native/PhoneTextInput.js

import React, { useCallback } from 'react'
import PropTypes from 'prop-types'
import { TextInput } from 'react-native'

/**
 * This is an _experimental_ React Native component.
 * Feedback thread: https://github.com/catamphetamine/react-phone-number-input/issues/296
 */
function PhoneTextInput({
  autoComplete,
  onChange,
  ...rest
}, ref) {
  // Instead of `onChangeText` it could use `onChange` and get `value` from `nativeEvent.text`.
  const onChangeText = useCallback((value) => {
    onChange({
      preventDefault() { this.defaultPrevented = true },
      target: { value }
    })
  }, [onChange])
  return (
    <TextInput
      ref={ref}
      autoCompleteType={autoComplete}
      keyboardType="phone-pad"
      onChangeText={onChangeText}
      {...rest}/>
  )
}

PhoneTextInput = React.forwardRef(PhoneTextInput)

PhoneTextInput.propTypes = {
  autoComplete: PropTypes.string,
  onChange: PropTypes.func.isRequired
}

export default PhoneTextInput

@gabrielmar cool!
@catamphetamine could you make this fix in the next release? It'd be very helpful.

Published react-phone-number-input@3.1.23

Thank you very much!

kav commented

Added a PR to allow inputComponent to be specified.
https://gitlab.com/catamphetamine/react-phone-number-input/-/merge_requests/10

the import statement "import PhoneInput from 'react-phone-number-input/react-native-input';" is giving a "could not find declaration file for module" error.

How can I work around/fix this issue?

kav commented

Is that actually a functional issue? I think typescript just wants you to add a .d

to what? theres a @types/react-phone-number-input but I don't see an equivalent for the react-native-input

kav commented

There isn't one, hence the error. Might be worth a PR but that's not causing functional issues is it?

I've followed the example exactly but the component isn't showing up on the emulator and that's the only issue vsc is highlighting

kav commented

I'm seeing the same warning but the control is working fine on the iOS simulator and react native web at the least, so it's likely something else. Any chance you can throw up a snack that shows your issue https://snack.expo.dev/?

Update: added inputComponent property on the React Native phone input component for things like "Material UI" input, etc. Didn't test it but I don't think it could possibly break. The component seems kinda matured at this point so I've removed the "experimental" note from the updated readme.

@cgrady3 I was planning to add TypeScript "typings" to this library this weekend. Maybe tomorrow.

@cgrady3
Added TypeScript "typings" in react-phone-number-input@3.1.32.
In case of any issues, open an issue.

@catamphetamine thank you for that. However, now it's giving the same error for every file it references (PhoneTextInput, PhoneInput, InputBasic, PropTypes, etc.)

I just wanted to mention something, I tried to implement using refs as suggested before but I didn't quite get it working. ChatGPT suggest using a prop I wasn't aware of called inputProps (doesn't seem to be in the docs) to be used alongside inputComponent and it works beautifully, I can now access any props in the TextInput via props I specify in the inputComponent prop