Rxova
Skip to content

Phone input — Migrating

Before migrating, be clear about what you are giving up: libphonenumber-backed packages do carrier-level validity, and this one does length-based possibility. If you rely on knowing that +1 555 555 5555 is unassignable, keep the metadata — or move that check to the server, where it costs nothing.

react-phone-number-input@rxova/react-phone-inputNotes
value / onChangevalue / onChangeBoth E.164; onChange also gets a details object
defaultCountrydefaultCountrySame, ISO 3166-1 alpha-2
country (forces a country)countryControlled; an explicit +… still wins
countriescountriesOrder is preserved here
internationalInferred: typing + switches modes
countryCallingCodeEditableAlways editable; that is what + is for
labels (a locale bundle)localeA BCP 47 tag; names come from Intl
flagsrenderCountryOr nothing — flags are emoji by default
numberInputPropsclassName / style / placeholderPlus the data-* hooks
isValidPhoneNumber(value)details.possibleWeaker. See above.
formatPhoneNumberformatPhoneExported
import { useState } from 'react'
import PhoneInput, { isValidPhoneNumber } from 'react-phone-number-input'
import 'react-phone-number-input/style.css'
function Signup() {
const [value, setValue] = useState('')
return (
<>
<PhoneInput defaultCountry="GB" value={value} onChange={setValue} />
<button type="submit" disabled={!value || !isValidPhoneNumber(value)}>
Continue
</button>
</>
)
}
import { useState } from 'react'
import { PhoneInput } from '@rxova/react-phone-input'
function Signup() {
const [value, setValue] = useState('')
const [possible, setPossible] = useState(false)
return (
<>
<PhoneInput
label="Phone"
defaultCountry="GB"
value={value}
onChange={(next, details) => {
setValue(next)
setPossible(details.possible)
}}
/>
<button type="submit" disabled={!possible}>
Continue
</button>
</>
)
}

You drop libphonenumber-js, country-flag-icons, input-format, classnames, prop-types and the stylesheet import — five runtime dependencies, and 41.1 kB brotli down to 3.9 kB.

Move it to where the metadata is free:

// On the server.
import { isValidPhoneNumber } from 'libphonenumber-js'
export function check(e164: string) {
return isValidPhoneNumber(e164)
}

The client stops a typo; the server decides whether the number is real. That split is usually what you wanted anyway, because a client-side check can always be bypassed.

react-phone-input-2 emits the number without a leading + by default and hands you a country object as a second argument, so the value shape changes.

react-phone-input-2@rxova/react-phone-inputNotes
value (no +)value (E.164, with +)Prefix your stored values once
onChange(value, country, e, formatted)onChange(e164, details)details.country is the ISO code
countrydefaultCountryLowercase ISO there, either case here
onlyCountriescountries
preferredCountriescountriesPut them first; order is preserved
enableSearchThe native select does type-ahead
inputPropsname, required, placeholder, …Ordinary props
import { useState } from 'react'
import { PhoneInput } from '@rxova/react-phone-input'
function Field() {
const [value, setValue] = useState('')
return (
<PhoneInput
label="Phone"
countries={['GB', 'IE', 'US']}
defaultCountry="GB"
value={value}
onChange={setValue}
/>
)
}

You also drop four lodash.* packages and prop-types.

The closest relative — also zero-dependency — but it bundles its own country and name data (8.3 kB brotli against this package’s 3.9 kB) rather than using Intl.

react-international-phone@rxova/react-phone-inputNotes
value / onChange({ phone, country })value / onChange(e164, details)details carries the same information
defaultCountrydefaultCountry
countries (country objects)countries (ISO codes)
forceDialCodeThe calling code is always visible in +… mode
disableDialCodePrefill
usePhoneInputusePhoneInputBoth expose a headless hook
import { PhoneInput } from '@rxova/react-phone-input'
function Field() {
return <PhoneInput label="Phone" defaultCountry="ua" />
}

If you are currently storing whatever the user typed, this is mostly a gain: you get a canonical E.164 value, a country, and a length check.

import { PhoneInput } from '@rxova/react-phone-input'
// Before
function Before() {
return <input type="tel" name="phone" />
}
// After — the form still posts under `phone`, now as E.164.
function After() {
return <PhoneInput label="Phone" name="phone" defaultCountry="US" />
}

Existing rows in your database will be in whatever shape they were saved in. parsePhone can normalise them, given a country to interpret national numbers against:

import { parsePhone } from '@rxova/react-phone-input'
parsePhone('(415) 555-2671', 'US').e164 // '+14155552671'
parsePhone('020 7123 4567', 'GB').e164 // '+442071234567'