catamphetamine / react-phone-number-input

React component for international phone number input
http://catamphetamine.gitlab.io/react-phone-number-input/
MIT License
933 stars 195 forks source link
number phone react

react-phone-number-input

npm version npm downloads

International phone number <input/> for React.

See Demo

Install

npm install react-phone-number-input --save

If you're not using a bundler then use a standalone version from a CDN.

The component uses libphonenumber-js for phone number parsing and formatting.

Use

The component comes in two variants: "with country select" and "without country select".

With country select

"With country select" component requires two properties: value and onChange(value). See the list of all available props.

import 'react-phone-number-input/style.css'
import PhoneInput from 'react-phone-number-input'

function Example() {
  // `value` will be the parsed phone number in E.164 format.
  // Example: "+12133734253".
  const [value, setValue] = useState()
  return (
    <PhoneInput
      placeholder="Enter phone number"
      value={value}
      onChange={setValue}/>
  )
}

The value argument of onChange(value) function will be the parsed phone number in E.164 format. For example, if a user chooses "United States" and enters (213) 373-4253 in the input field then onChange(value) will be called with value being "+12133734253".

Any "falsy" value like undefined, null or an empty string "" is treated like "empty". In case of the onChange() function's value argument though it's always undefined for an "empty" value, i.e. when the user erases the input value, onChange() is called with undefined as an argument. Perhaps null would've been better, but historically it has been undefined.

All unknown properties will be passed through to the phone number <input/> component.

To set a default country, pass a defaultCountry property (must be a supported country code). Example: <PhoneInput defaultCountry="US" .../>.

To get the currently selected country, pass an onCountryChange(country) property.

To get the country of a complete phone number, use parsePhoneNumber(value): parsePhoneNumber(value) && parsePhoneNumber(value).country.

To format value back to a human-readable phone number, use formatPhoneNumber(value) or formatPhoneNumberIntl(value).

CSS

"With country select" component comes with a style.css stylesheet. All CSS class names start with .PhoneInput. Additional "modifier" CSS classes: .PhoneInput--focus for :focus, .PhoneInput--disabled for :disabled, .PhoneInput--readOnly for [readonly].

The stylesheet uses native CSS variables for convenience. Native CSS variables work in all modern browsers, but older ones like Internet Explorer wont't support them. For compatibility with such older browsers one can use a CSS transformer like PostCSS with a "CSS custom properties" plugin like postcss-custom-properties.

Some of the CSS variables:

When using Webpack

When using Webpack, include the stylesheet on a page via import:

import 'react-phone-number-input/style.css'

For supporting old browsers like Internet Explorer, one could use postcss-loader with a CSS autoprefixer and postcss-custom-properties transpiler.

When not using Webpack

Get style.css file from this package, optionally process it with a CSS autoprefixer and postcss-custom-properties transpiler for supporting old web browsers, and then include the CSS file on a page.

<head>
  <link rel="stylesheet" href="https://github.com/catamphetamine/react-phone-number-input/blob/master/css/react-phone-number-input/style.css"/>
</head>

Or include the style.css file directly from a CDN if you don't have to support Internet Explorer.

Without country select

"Without country select" component is just a phone number <input/>.

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

function Example() {
  // `value` will be the parsed phone number in E.164 format.
  // Example: "+12133734253".
  const [value, setValue] = useState()
  // If `country` property is not passed
  // then "International" format is used.
  // Otherwise, "National" format is used.
  return (
    <PhoneInput
      country="US"
      value={value}
      onChange={setValue} />
  )
}

Doesn't require any CSS.

Receives properties:

See the demo for the examples.

For those who want to pass custom metadata there's react-phone-number-input/input-core sub-package.

This library also exports getCountries() and getCountryCallingCode(country) functions that a developer could use to construct their own custom country select. Such custom country <select/> could be used in conjunction with the "without country select" <input/> described above.

Creating a custom country <select/> #### ```js import PropTypes from 'prop-types' import { getCountries, getCountryCallingCode } from 'react-phone-number-input' const CountrySelect = ({ value, onChange, labels, ...rest }) => ( ) CountrySelect.propTypes = { value: PropTypes.string, onChange: PropTypes.func.isRequired, labels: PropTypes.objectOf(PropTypes.string).isRequired } ``` Use: ```js import PhoneInput from 'react-phone-number-input/input' import en from 'react-phone-number-input/locale/en' import CountrySelect from './CountrySelect' function Example() { const [country, setCountry] = useState('US') const [value, setValue] = useState() return (
) } ```

React Native

This library also includes a React Native version of a "without country select" component. Post bug reports and suggestions in the feedback thread.

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

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

Accepts the same properties as the web version of "without country select" component, with the following differences:

Utility

This package exports several utility functions.

formatPhoneNumber(value: string): string

Formats value as a "local" phone number.

import { formatPhoneNumber } from 'react-phone-number-input'
formatPhoneNumber('+12133734253') === '(213) 373-4253'

formatPhoneNumberIntl(value: string): string

Formats value as an "international" phone number.

import { formatPhoneNumberIntl } from 'react-phone-number-input'
formatPhoneNumberIntl('+12133734253') === '+1 213 373 4253'

isPossiblePhoneNumber(value: string): boolean

Checks if a phone number value is a "possible" phone number. A phone number is "possible" when it has valid length. The actual phone number digits aren't validated.

import { isPossiblePhoneNumber } from 'react-phone-number-input'
isPossiblePhoneNumber('+12223333333') === true

isValidPhoneNumber(value: string): boolean

Checks if a phone number value is a "valid" phone number. A phone number is "valid" when it has valid length, and the actual phone number digits match the regular expressions for that country.

import { isValidPhoneNumber } from 'react-phone-number-input'
isValidPhoneNumber('+12223333333') === false
isValidPhoneNumber('+12133734253') === true

By default the component uses min "metadata" which results in less strict validation compared to max or mobile.

I personally don't use isValidPhoneNumber() for phone number validation in my projects. The rationale is that telephone numbering plans can and sometimes do change, meaning that isValidPhoneNumber()function may one day become outdated on a website that isn't actively maintained anymore. Imagine a "promo-site" or a "personal website" being deployed once and then running for years without any maintenance, where a client may be unable to submit a simple "Contact Us" form just because this newly allocated pool of mobile phone numbers wasn't present in that old version of libphonenumber-js bundled in it.

Whenever there's a "business requirement" to validate a phone number that's being input by a user, I prefer using isPossiblePhoneNumber() instead of isValidPhoneNumber(), so that it just validates the phone number length, and doesn't validate the actual phone number digits. But it doesn't mean that you shouldn't use isValidPhoneNumber() — maybe in your case it would make sense.

parsePhoneNumber(input: string): PhoneNumber?

Parses a PhoneNumber object from a string. This is simply an alias for parsePhoneNumber() from libphonenumber-js. Can be used to get country from value.

import { parsePhoneNumber } from 'react-phone-number-input'
const phoneNumber = parsePhoneNumber('+12133734253')
if (phoneNumber) {
  phoneNumber.country === 'US'
}

getCountryCallingCode(country: string): string

Returns the "country calling code" of a country. The country argument must be a supported country code.

This is simply an alias for getCountryCallingCode() from libphonenumber-js.

import { getCountryCallingCode } from 'react-phone-number-input'
getCountryCallingCode('US') === '1'

isSupportedCountry(country: string): boolean

Checks if a country is supported by this library.

This is simply an alias for isSupportedCountry() from libphonenumber-js.

import { isSupportedCountry } from 'react-phone-number-input'
isSupportedCountry('US') === true

Flags URL

By default, all flags are linked from country-flag-icons's GitHub pages website as <img src="https://github.com/catamphetamine/react-phone-number-input/raw/master/.."/>s. Any other flag icons could be used instead by passing a custom flagUrl property (which is "https://purecatamphetamine.github.io/country-flag-icons/3x2/{XX}.svg" by default) and specifying their aspect ratio via --PhoneInputCountryFlag-aspectRatio CSS variable (which is 1.5 by default, meaning "3x2" aspect ratio).

For example, using flagpack "4x3" flag icons would be as simple as:

:root {
  --PhoneInputCountryFlag-aspectRatio: 1.333;
}
<PhoneInput flagUrl="https://flag.pk/flags/4x3/{xx}.svg" .../>

Including all flags

Linking flag icons as external <img/>s is only done to reduce the overall bundle size, because including all country flags in the code as inline <svg/>s would increase the bundle size by 44 kB (after gzip).

If bundle size is not an issue (for example, for a standalone non-web application, or an "intranet" application), then all country flags can be included directly in the code by passing the flags property:

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

<PhoneInput flags={flags} .../>

Localization

Language translations can be applied using the labels property. This component comes pre-packaged with several translations. Submit pull requests for adding new language translations.

Where to get country names for any language. #### Country names can be copy-pasted from [`github.com/umpirsky/country-list`](https://github.com/umpirsky/country-list/blob/master/data/). ```js JSON.stringify( Object.keys(countries).sort() .reduce((all, country) => ({ ...all, [country]: countries[country] }), {}), null, '\t' ) ```` Also note that a country names list generated from `umpirsky/country-list` won't include Ascension Island (`AC`) and Tristan da Cunha (`TA`) — they will need to be added manually.

The labels format is:

{
  // Can be used as a label for country input.
  // Country `<select/>` uses this as its default `aria-label`.
  "country": "Phone number country",
  // Can be used as a label for phone number input.
  "phone": "Phone",
  // Can be used as a label for phone number extension input.
  "ext": "ext.",
  // Country names.
  "AB": "Abkhazia",
  "AC": "Ascension Island",
  ...,
  "ZZ": "International"
}

An example of using translated labels:

import ru from 'react-phone-number-input/locale/ru'

<PhoneInput ... labels={ru}/>

min vs max vs mobile

This component uses libphonenumber-js which provides different "metadata" sets, "metadata" being a list of phone number parsing and formatting rules for all countries. The complete list of those rules is huge, so libphonenumber-js provides a way to optimize bundle size by choosing between max, min, mobile and "custom" metadata:

To use a particular metadata set, simply import functions from a relevant sub-package.

For "with country select" component those're:

Importing functions directly from react-phone-number-input effectively results in using the min metadata.

For "without country select" component the sub-packages are:

Sometimes (rarely) not all countries are needed, and in those cases developers may want to generate their own "custom" metadata set. For those cases, there's a /core sub-package that doesn't come pre-packaged with any default metadata set and instead accepts metadata as a component property and as the last argument of each exported function.

For "with country select" component, the /core export is react-phone-number-input/core, and for "without country select" component, the /core export is react-phone-number-input/input-core.

Bug reporting

If you think that the phone number parsing/formatting/validation engine malfunctions for a particular phone number then it could be for several reasons:

Autocomplete

Make sure to put a <PhoneInput/> into a <form/> otherwise web-browser's "autocomplete" feature may not be working: a user will be selecting his phone number from the list but nothing will be happening.

react-hook-form

To use this component with react-hook-form, use one of the four exported components:

// "Without country select" component.
import PhoneInput from 'react-phone-number-input/react-hook-form-input'

// "Without country select" component (to pass custom `metadata` property).
import PhoneInput from 'react-phone-number-input/react-hook-form-input-core'

// "With country select" component.
import PhoneInputWithCountry from 'react-phone-number-input/react-hook-form'

// "With country select" component (to pass custom `metadata` property).
import PhoneInputWithCountry from 'react-phone-number-input/react-hook-form-core'

Example:

// "Without country select" component.
import PhoneInput from "react-phone-number-input/react-hook-form-input"

// "With country select" component.
import PhoneInputWithCountry from "react-phone-number-input/react-hook-form"

import { useForm } from "react-hook-form"

export default function Form() {
  const {
    // Either pass a `control` property to the component
    // or wrap it in a `<FormProvider/>`.
    control,
    handleSubmit
  } = useForm()

  return (
    <form onSubmit={handleSubmit(...)}>
      <PhoneInput
        name="phoneInput"
        control={control}
        rules={{ required: true }} />

      <PhoneInputWithCountry
        name="phoneInputWithCountrySelect"
        control={control}
        rules={{ required: true }} />

      <button type="submit">
        Submit
      </button>
    </form>
  )
}

Both components accept properties:

Customization

"With country select" <PhoneInput/> component accepts some customization properties:

All those customization properties have their default values which are, therefore, always included in the application bundle, regardless of whether those default property values get overridden by any custom ones.

Those who'd like to exclude the default values just for metadata and labels properties could import the component from react-phone-number-input/core subpackage rather than from react-phone-number-input package.

countrySelectComponent

React component for the country select. See CountrySelect.js for an example.

Receives properties:

inputComponent

A React component for the phone number input field. Is "input" by default, meaning that it renders a standard DOM <input/>.

Any custom input component implementation must use React.forwardRef() to "forward" ref to the underlying "core" <input/> component.

Receives properties:

flagComponent

Renders a country flag icon.

Receives properties:

internationalIcon

Renders an "International" icon. For example, the default one is a globe icon. The icon is shown instead of a country flag when the phone number is in international format (i.e. starts with a + character) but is either incomplete or doesn't belong to any known country.

Receives properties:

CDN

One can use any npm CDN service, e.g. unpkg.com or jsdelivr.net

<!-- Default ("min" metadata). -->
<script src="https://unpkg.com/react-phone-number-input@3.x/bundle/react-phone-number-input.js"></script>

<!-- Or "max" metadata. -->
<script src="https://unpkg.com/react-phone-number-input@3.x/bundle/react-phone-number-input-max.js"></script>

<!-- Or "mobile" metadata. -->
<script src="https://unpkg.com/react-phone-number-input@3.x/bundle/react-phone-number-input-mobile.js"></script>

<!-- Styles for the component. -->
<!-- Internet Explorer requires transpiling CSS variables. -->
<link rel="stylesheet" href="https://unpkg.com/react-phone-number-input@3.x/bundle/style.css"/>

<script>
  var PhoneInput = window.PhoneInput.default
</script>

Without country select:

<!-- Without country `<select/>` ("min" metadata). -->
<script src="https://unpkg.com/react-phone-number-input@3.x/bundle/react-phone-number-input-input.js"></script>

<script>
  var PhoneInput = window.PhoneInput.default
</script>

Country code

A "country code" is a two-letter ISO country code (like US).

This library supports all officially assigned ISO alpha-2 country codes, plus a few extra ones like: AC (Ascension Island), TA (Tristan da Cunha), XK (Kosovo).

To check whether a country code is supported, use isSupportedCountry() function.

TypeScript

This library comes with TypeScript "typings". If you happen to find any bugs in those, create an issue.

Tests

This component comes with 100% code coverage for the core ./source/helpers directory.

To run tests:

npm test

To generate a code coverage report:

npm run test-coverage

The code coverage report can be viewed by opening ./coverage/lcov-report/index.html.

If the code coverage report is "empty" then it means that a newer version of handlebars was accidentally installed and should be reverted to handlebars@4.5.3.

The handlebars@4.5.3 workaround in devDependencies is for the test coverage to not produce empty reports:

Handlebars: Access has been denied to resolve the property "statements" because it is not an "own property" of its parent.
You can add a runtime option to disable the check or this warning:
See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details

GitHub

On March 9th, 2020, GitHub, Inc. silently banned my account (erasing all my repos, issues and comments) without any notice or explanation. Because of that, all source codes had to be promptly moved to GitLab. GitHub repo is now deprecated, and the latest source codes can be found on GitLab, which is also the place to report any issues.

License

MIT