catamphetamine/libphonenumber-js

Can not delete phone number in US National format when the input string has 3 characters

Closed this issue ยท 15 comments

I'm using AsYouType to format the input phone number, I got an error when attempting to delete phone number by press Backspace when the input string has 3 characters.

Check out the gif and the code below:
asyoutype-error

new AsYouType('US').input(value)

This library doesn't provide any input components, it just provides a set of javascript functions.

The issue you're experiencing happens most likely because you parse and format the <input/> value every time change event is triggered.
For example, when the input value is "(714)" and the user hits backspace, then the input value is "(714", and then the code parses it into 714 and passes these digits to new AsYouType.input("714") and writes the result in the <input/> resulting in "(714)" again, so it looks as if Backspace didn't erase anything.

The fix could be:

import { parseIncompletePhoneNumber, formatIncompletePhoneNumber } from 'libphonenumber-js'

let newValue = parseIncompletePhoneNumber(event.target.value)
// By default, if a value is something like `"(123)"`
// then Backspace would only erase the rightmost brace
// becoming something like `"(123"`
// which would give the same `"123"` value
// which would then be formatted back to `"(123)"`
// and so a user wouldn't be able to erase the phone number.
// Working around this issue with this simple hack.
if (newValue === value) {
  const newValueFormatted = formatIncompletePhoneNumber(newValue, country)
  if (newValueFormatted.indexOf(event.target.value) === 0) {
    // Trim the last digit (or plus sign).
    newValue = newValue.slice(0, -1)
  }
}
onChange(newValue)

@whimsicaldreamer Well, ok, I did encounter a similar bug, and that was because in onChange I parsed the whole input and then re-formatted it.

<input onChange={ value => this.setState({ value: format(parse(value)) }) }/>

So because a brace is not a significant character (123) -> (123 was parsed to 123 and then re-formatted back to (123) so it was as if Backspace "didn't work" on braces.

The described use case is for parse/format though, not for "as you type" to which the OP referred.

Yeah, the code comment has the well explained here..

@whimsicaldreamer And with AsYouType, you only apply it when the input contains more than 3 characters, maybe this regex /(.?\d){4,}/.test(input) will help you detect when to start applying AsYouType. Nothing to format with just 3 numbers isn't?

@catamphetamine I manually tested my use cases and it turned out to be correct. Although on testing on a mobile device the input field is text type but other demos of react-phone-number-input opens up the number pad.

Secondly, in the mobile device typing in the digits were smooth. Removal of numbers at a go was also smooth. But when I was removing characters one by one, I found that I needed to press the backspace key twice to remove characters in the brackets. (This issue goes away when I set the input field type to tel)

Example: Suppose typed in characters are (213) 45| and | is the position of cursor
1 backspace -> (213) 4|
1 backspace -> (213)|
1 backspace -> (213)|
1 backspace -> (21 |)
1 backspace -> (21| )
1 backspace -> 2|
1 backspace -> empty field

@nguyennb9 I was able to work around this by only formatting once the character count is over 3 digits. What I believe to be happening is that when you hit the backspace, the field is still evaluating to 3 digits which will add the parenthesis for US format. Adding in a conditional to start formatting after 3 digits have been entered worked well for me.

@jharsono yeah, as I said #225 (comment)
I think we are done with this issue.

@jharsono I am hesitant to hardcode 3 because I don't know how other countries format their phone numbers. Is the US the only country using parentheses?

I wasn't super happy with any of the solutions here, so here's what I ended up doing. Hopefully this helps someone out :)

import {
  formatIncompletePhoneNumber,
} from 'libphonenumber-js';

...

// onChangeText is a react-native TextInput thing fyi
const _onChangeText = (text: string) => {
  let formattedPhoneNumber = formatIncompletePhoneNumber(text, countryCode);
  // Handle delete
  if (formattedPhoneNumber === props.value) {
    const digits = formattedPhoneNumber.replace(/\D/g,''); //strip away non-numeric characters
    const withoutLastDigit = digits.slice(0, digits.length - 1); //remove the last digit
    formattedPhoneNumber = formatIncompletePhoneNumber(withoutLastDigit, countryCode);
  }
  onChangeText(formattedPhoneNumber);
};

I am was looking for an answer but don't really like to start applying AsYouType after 3 digits..
Here is what I came up with, hope it helps others:

export const formatPhoneNumber = (value?: string | number) => {
    if (!value) return '';
    value = value.toString();
    if (value.includes('(') && !value.includes(')')) {
        return value.replace('(', '');
    }
    return new AsYouType('US').input(value);
};

This is kind of a helper function I use to include in input onchange if input type is phone

I also encountered this issue & struggled with this for a minute & didn't find any solutions I liked here. So I am posting what I ended up doing in case it helps someone else out.

import { useState } from "react";
import { AsYouType } from "libphonenumber-js";
....
  const [cellNumber, updateCellNumber] = useState("");
  
  const formatCellNumber = (number) => {
    const asYouType = new AsYouType("US");
    asYouType.input(number);
    return asYouType;
  };
  
  const formattedCellNumber = formatCellNumber(cellNumber);
  return (
    <input
      placeholder="317-867-5309"
      name="cellphone"
      value={formattedCellNumber.formattedOutput}
      onChange={(event) => {
        let newValue = event.target.value;
        const newFormattedValue = formatCellNumber(newValue).formattedOutput;
        /*
         Makes sure a character was removed from string && the new formatted value equals current formatted
         value before removing digit
        */
        if (newValue.length < cellNumber.length && newFormattedValue === cellNumber) {
          newValue = newValue.slice(0, -1);
        }
        updateCellNumber(formatCellNumber(newValue).formattedOutput);
      }}
    />
  );

This fixes the original post issue however there are still issues I have observed even with this i.e.
given a string that matches this template: x (xxx) xxx-xxxx
when type a string like x (xxx) xxx-xxxx & I place my cursor anywhere where there is a space or special character +()- & I remove it the number at the end of string will be removed instead with the given code its not perfect but its good enough to get me through this for now until I figure something else out

I am was looking for an answer but don't really like to start applying AsYouType after 3 digits..
Here is what I came up with, hope it helps others:

This fixed it for me, so thank you. But since it still returns 3 digits, which causes the field to change, triggering the event again and making it auto-format right back into what it was, I added one more line to your code snippet to also delete the last digit in parenthesis:

if (value.includes('(') && !value.includes(')')) {
      const tempValue = value.replace('(', '');
      return tempValue.slice(0, tempValue.length - 1);
}

Fixed it by just applying the formatter when the inputValue was greater than 4 since no country uses an area code of 4 digits.

It worked for me by removing the parentheses if the country number is not valid with the number of characters.

export const formatPhoneNumber = (phoneNumber: AsYouType): string => {
    const rawNumber = phoneNumber.getChars();
    const country = phoneNumber.getCountry();

    phoneNumber.reset();

    const validCountryPhoneNumber =
	validatePhoneNumberLength(rawNumber, country) === undefined;

        if (!validCountryPhoneNumber) {
	    return phoneNumber.input(rawNumber).replace('(', '').replace(')', '');
	}

    return phoneNumber.input(rawNumber);
};
"libphonenumber-js": "^1.10.60"