garganurag893 / react-native-phone-number-input

React Native component for phone number.
MIT License
377 stars 219 forks source link

AsYouTypeFormatter #19

Open kickbk opened 4 years ago

kickbk commented 4 years ago

Thanks for the work you put in this! Nice utility.

Formatting How would you incorporate AsYouTypeFormatter from google-libphonenumber into it? I have:

import { PhoneNumberUtil, AsYouTypeFormatter } from "google-libphonenumber";
const phoneUtil = PhoneNumberUtil.getInstance();
const formatter = new AsYouTypeFormatter('US');

And it will indeed format the number as we type if we change to

onChangeText = (text) => {
    this.setState({ number: text });
    const { onChangeText, onChangeFormattedText } = this.props;
    if (onChangeText) {
      const formatted = formatter.inputDigit(text.slice(text.length - 1))
      onChangeText(formatted);
    }
    if (onChangeFormattedText) {
      const { code } = this.state;
      if (code) {
        onChangeFormattedText(text.length > 0 ? `+${code}${text}` : text);
      } else {
        onChangeFormattedText(text);
      }
    }
  };

But it gets screwed up once we type delete. It's probably due to saving the number in state. How would you recommend handling this?

Pasting This is less important, but could be nice to have. I'm passing textContentType: 'telephoneNumber', via textInputProps. textContentType (on iOS) allows one click number paste. Any suggestions on handling pasting of numbers and then formatting if needed? Maybe some type of paste(it's probably the same as type) > strip > format ?

KerrySng99 commented 4 years ago

I got parsePhoneNumber working from libphonenumber-js https://gitlab.com/catamphetamine/libphonenumber-js which is a similar formatter to AsYouType

`import PhoneInput from 'react-native-phone-number-input'; import { parsePhoneNumber} from 'libphonenumber-js'

onChange = (text) => { const phoneInput = this.mRef const countryCode = phoneInput.current.getCountryCode() console.log('country code ===>', countryCode)

    console.log("PHONE INPUT===>", phoneInput);
    if(text.length > 5){
        const phoneNumber = parsePhoneNumber(text)
        this.setState({
            setFormattedMobile: 
             phoneNumber.format("INTERNATIONAL").replace(/\s/g, '')
        })
    }

} render() { return( <PhoneInput ref={this.mRef} onChangeText={text => this.setState({mNumber:text})} onChangeFormattedText={this.onChange} placeholder="Mobile Number" defaultValue={this.state.mNumber}
/> ) }`

kickbk commented 4 years ago

@KerrySng99 thanks for sharing your code, but this is not "as you type" solution. You only parse when the length is 5. Also, it doesn't account for pasting a whole number from the phone. For instance, with textContentType="telephoneNumber" option passed to the TextInput, it will allow for a quick one-click pasting of your phone number. That number may contain non-numeric characters as well, as in (555) 555-5555. Still hoping @garganurag893 will drop a word on this. Not sure if this library is maintained though.

theodelebarre commented 3 years ago

Hey all. i was able to do that using libphonenumber-js.

You need: 1 - import { AsYouType } from "libphonenumber-js"; 2 - const phoneFormatAsYouType = new AsYouType(phoneInput.current.getCountryCode()); 3- onChangeText={(text) => { setPhoneValue(phoneFormatAsYouType.input(text)); }}

kickbk commented 3 years ago

@theodelebarre I haven't tested it thoroughly and on different devices, but it seems to work. thanks for sharing

kickbk commented 3 years ago

It working nicely, however, once I set the default value to a formatted value, it gets all messed up when you start editing the number.

What I mean is this: Say I have a user phone number as 5555555555. when the user loads the update your phone number screen, I want to show the phone number already formatted, so I do this:

<PhoneInput
    ref={phoneInput}
    defaultValue={phoneFormatAsYouType.input(value)}
    defaultCode="US"
    onChangeText={(text) => {
        setValue(phoneFormatAsYouType.input(text));
    }}
    onChangeFormattedText={(text) => {
        setFormattedValue(text);
    }}
    textInputProps={{
        maxLength: 14,
    }}
/>

I'm passing a formatted value with defaultValue={phoneFormatAsYouType.input(value)}. It shows up nicely, but once you start typing it gets screwed up.

What works is passing defaultValue={value} - It will not show the formatted value right away, but once the user starts editing, it will function correctly. But that's not so nice.

Any ideas how to fix this?

ajlamarc commented 3 years ago

@theodelebarre His solution works excellently for me, however when I go over the max length it jumps to being un-formatted. Is there a way to dynamically set the maxLength of the text input (based on country) so that this can't happen?

theodelebarre commented 3 years ago

@kickbk @ajlamarc Hey, sorry I haven't seen those comments before, I think you retrieved a value that was previously sent unformatted, I had that issue before, but then I cleared my test users, started again with that input set everywhere I needed it, the phone number saved is formatted, and when returned it perfectly displays itself.

For the other issue where it gets unformatted if too long, I think libphonenumber.js does that by itself, maybe it is just a visual feedback saying here you're phone number's wrong, we can't format it, but I'm sure you can play around with this library to detect the country of the phone number typed in and format it differently.

Have fun coding! :P

kickbk commented 3 years ago

Merci Théo. That definitely helped. It now mostly works. So far, what doesn't work, while testing on the simulator, is deleting a digit in the middle of the phone number using the "Delete to end of line" with Ctrl-D, or the "delete ->" button on a Mac keyboard. It removes a digit, adds an additional bracket in the beginning of the line instead of trimming the number.

However, I may be able to live with this since on a mobile device you cannot delete in such a way. If you can think of why it does it and if there's a solution, that would be much better of course.

delete

theodelebarre commented 3 years ago

@kickbk Yeah I found out as well.. I'm still trying to enhance it from time to time until I won't have any issue. I will share asap if I'm able to find something :P

WangHansen commented 3 years ago

I got it working with google-libphonenumber by patching the library code:

onChangeText = (text) => {
    const originalNumber = (text.match(/\d/g) ?? []).join("");
    const { onChangeText, onChangeFormattedText } = this.props;
    const formatter = new AsYouTypeFormatter(this.state.countryCode || 'US');
    let formatted = '';
    formatter.clear();
    for (let c of originalNumber) {
      formatted = formatter.inputDigit(c);
    }
    this.setState({ number: formatted });
    if (onChangeText) {
      onChangeText(originalNumber);
    }
    if (onChangeFormattedText) {
      const { code } = this.state;
      if (code) {
        onChangeFormattedText(originalNumber.length > 0 ? `+${code}${originalNumber}` : originalNumber);
      } else {
        onChangeFormattedText(originalNumber);
      }
    }
  };

I submitted a PR: #51

pawel3ala commented 3 years ago

works like a charm ;)

efstathiosntonas commented 3 years ago

Thanks @WangHansen, worked like a charm

here's a patch for patch-package until PR is merged:

filename: react-native-phone-number-input+2.1.0.patch

diff --git a/node_modules/react-native-phone-number-input/lib/index.js b/node_modules/react-native-phone-number-input/lib/index.js
index b89c388..3bb557b 100644
--- a/node_modules/react-native-phone-number-input/lib/index.js
+++ b/node_modules/react-native-phone-number-input/lib/index.js
@@ -7,7 +7,7 @@ import CountryPicker, {
   CountryModalProvider,
   Flag,
 } from "react-native-country-picker-modal";
-import { PhoneNumberUtil } from "google-libphonenumber";
+import { PhoneNumberUtil, AsYouTypeFormatter } from "google-libphonenumber";
 import styles from "./styles";

 const dropDown =
@@ -92,17 +92,24 @@ export default class PhoneInput extends PureComponent {
   };

   onChangeText = (text) => {
-    this.setState({ number: text });
+    const originalNumber = (text.match(/\d/g) ?? []).join("");
     const { onChangeText, onChangeFormattedText } = this.props;
+    const formatter = new AsYouTypeFormatter(this.state.countryCode || 'US');
+    let formatted = '';
+    formatter.clear();
+    for (let c of originalNumber) {
+      formatted = formatter.inputDigit(c);
+    }
+    this.setState({ number: formatted });
     if (onChangeText) {
-      onChangeText(text);
+      onChangeText(originalNumber);
     }
     if (onChangeFormattedText) {
       const { code } = this.state;
       if (code) {
-        onChangeFormattedText(text.length > 0 ? `+${code}${text}` : text);
+        onChangeFormattedText(originalNumber.length > 0 ? `+${code}${originalNumber}` : originalNumber);
       } else {
-        onChangeFormattedText(text);
+        onChangeFormattedText(originalNumber);
       }
     }
   };