The Principal Dev – Masterclass for Tech Leads

The Principal Dev – Masterclass for Tech Leads28-29 May

Join

react-phone-number-input

npm version npm downloads

International phone number <input/> for React.

See Demo

Install

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

Alternatively, one could include it on a web page directly via a <script/> tag.

Use

This package exports two variants of the input component, depending on what you prefer:

With country select

"With country select" component requires only two properties: value and onChange(value).

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

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

function Example() {
  const [value, setValue] = useState()
  return (
    <PhoneInput
      placeholder="Enter phone number"
      value={value}
      onChange={setValue}
    />
  )
}

More on the properties:

CSS

"With country select" component requires including style.css stylesheet on the page.

All CSS class names start with .PhoneInput prefix in order to not conflict with the application styles.

The following "status modifier" CSS classes are available:

The stylesheet uses native CSS variables for convenience. Native CSS variables have been supported in all modern browsers for a long time now, but ancient ones like Internet Explorer don't support them. If compatibility with such ancient browsers is required, one could use a CSS transformer like PostCSS with a "CSS custom properties" plugin.

Some of the CSS variables of interest:

Without country select

"Without country select" component is just a minimal "bare-bones" phone number <input/>, without any CSS.

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

function Example() {
  // `value` holds the parsed phone number (in E.164 format).
  // When the input is empty, or when there's not enough digits, value is `undefined`.
  //
  // Example: "+12133734253".
  //
  const [value, setValue] = useState()

  // Specifying a `country` only allows input of phone numbers belonging to that specific country.
  // Not specifying a `country` only allows input of any phone number in international format
  // (i.e. starting with a "+").
  //
  return (
    <PhoneInput
      country="US"
      value={value}
      onChange={setValue}
    />
  )
}

Available properties:

See the demo for the examples.

If you're an "advanced" user who'd like to pass their custom libphonenumber-js metadata, use react-phone-number-input/input-core component instead — it accepts metadata property.

This package 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 alongside the "without country select" <input/> component.

How to create a custom country <select/>

import PropTypes from 'prop-types'
import { getCountries, getCountryCallingCode } from 'react-phone-number-input'

const CountrySelect = ({ value, onChange, labels, ...rest }) => (
  <select
    {...rest}
    value={value}
    onChange={event => onChange(event.target.value || undefined)}>
    <option value="">
      {labels['ZZ']}
    </option>
    {getCountries().map((country) => (
      <option key={country} value={country}>
        {labels[country]} +{getCountryCallingCode(country)}
      </option>
    ))}
  </select>
)

CountrySelect.propTypes = {
  value: PropTypes.string,
  onChange: PropTypes.func.isRequired,
  labels: PropTypes.objectOf(PropTypes.string).isRequired
}

Use:

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 (
    <div>
      <CountrySelect
        labels={en}
        value={country}
        onChange={setCountry}/>
      <PhoneInput
        country={country}
        value={value}
        onChange={setValue}/>
    </div>
  )
}

React Native

This package 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={...}
      country="US"
      value={value}
      onChange={setValue}
    />
  )
}

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

Validation

To validate the phone number input value, use the exported isPossiblePhoneNumber(value) function.

import { isPossiblePhoneNumber } from 'react-phone-number-input'

const isValid = (value) => Boolean(value) && isPossiblePhoneNumber(value)

What to do next with the returned boolean value is up to each different application. Most applications use frameworks like react-hook-form or formik or a gazillion of other ones. Those frameworks each have their own way of setting up validation.

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 the value could be a "possible" phone number. In other words, it checks if the phone number length is correct. The actual phone number digits themselves aren't validated.

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

isValidPhoneNumber(value: string): boolean

Checks if the value represents a "valid" phone number. In other words, it checks if the phone number length is correct, and all digits are correct too.

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

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

How to choose between isPossiblePhoneNumber() and isValidPhoneNumber(): I'd personally prefer isPossiblePhoneNumber() because its strength is in its weakness. isValidPhoneNumber() is a double-edged sword in terms of how strict it is, and when not kept up-to-date, it could get stale over time and start rejecting freshly-assigned phone number ranges.

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'
} else {
  // The argument is not a valid phone number
}

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 given country code 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="..."/>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 custom "4x3" flag icons would be as simple as:

:root {
  --PhoneInputCountryFlag-aspectRatio: 1.333;
}
<PhoneInput flagUrl="https://example.com/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 translation can be applied by passing a custom labels property value. This component comes pre-packaged with several importable translations.

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

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

If labels for a certain language are missing, one could submit a pull request to add those.

Where could one get the list of country names for a given language.

There's a myriad sources on the internet. Modern web browsers even have an official built-in list of country names in all languages.

For example, one could copy country names from github.com/umpirsky/country-list.

import countryNamesInRussian from 'country-list/data/ru/country.json'

// Outputs a JSON with the country names.
JSON.stringify(
  Object.keys(countryNamesInRussian).sort()
    .reduce((all, country) => ({
      ...all,
      [country]: countries[country]
    }), {}),
  null,
  '\t'
)

Note that this library uses the term "country code" rather broadly, including both the official and ISO-3166-1 country codes and a few of unofficial "country codes", so a translation should include the labels for both official and unofficial "country codes".

Also, a translation should include the following miscellaneous labels:

The final format for a translation file is:

{
  "country": "Phone number country",
  "phone": "Phone",
  "ext": "ext.",
  // The rest are country names, including "unofficial" ones
  // like `AC`, `TA`, `XK`, and `ZZ` for "International".
  ...,
  "RO": "Romania",
  "RS": "Serbia",
  "RU": "Russia",
  ...,
  "ZZ": "International"
}

min vs max vs mobile

This component uses libphonenumber-js which lets a developer choose from different "metadata" sets, where a "metadata" set is a complete list of phone number parsing and formatting rules for all possible countries.

As one may guess, the complete list of those rules is huge, so this package provides a way to optimize the bundle size by choosing between max, min, mobile or "custom" metadata, depending on the project's needs.

Choose one from the above and then simply import the components or functions from the relevant sub-package.

For "with country select" component, the import paths are:

For "without country select" component, the import paths are:

As for "custom" metadata, it could be used in those rare cases when not all countries are needed and a developer would really prefer to reduce the bundle size to a minimum. In that case, one could generate their own "custom" metadata set and then import the functions from react-phone-number-input/core or react-phone-number-input/input-core sub-package which doesn't come pre-packaged with any metadata and instead requires metadata property be passed.

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

There's a feature of a web browser when it automatically populates the input with the user's own phone number. It's called "autocomplete".

To enable this feature, make sure you're putting <PhoneInput/> component inside a <form/>, otherwise this feature may not be working: the user will be tapping on their phone number "suggestion" but nothing would 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

To include this library directly via a <script/> tag on a page, one can use any npm CDN service, e.g. unpkg.com or jsdelivr.com

<!-- 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", "CA", etc.

However, this library uses libphonenumber-js's variant of the "country code" term, which is rather broad and includes both the official ISO country codes and a few of unofficial "country codes". For that reason, a developer should use a "country code" returned from this library with caution in an application that only expects the official ISO "country codes" to exist. For example, such application will likely not have a label or a flag for such an unofficial "country code". In that case, a developer could manually transform an unofficial "country code" returned from this library to an official ISO country code of the most suitable "parent" country.

To check whether a certain two-letter "country code" is supported by this library, use isSupportedCountry() function.

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

Join libs.tech

...and unlock some superpowers

GitHub

We won't share your data with anyone else.