Skip to main content

Phone Number Validation

Validation is backed by libphonenumber-js, so a number is valid only if it matches a real numbering plan for the selected country — not just a digit count.

There are three ways to read validity. Pick one per form.

1. Reactive — onValidationChange

Best when the UI reacts to validity (enable a button, show an error).

import React, {useState} from 'react';
import {Button, Text, View} from 'react-native';
import PhoneInput, {ICountry, PhoneNumberType} from 'rn-international-phone-number';

export default function App() {
const [phone, setPhone] = useState('');
const [country, setCountry] = useState<ICountry | null>(null);
const [isValid, setIsValid] = useState(false);

function handleValidation(
valid: boolean,
type: PhoneNumberType | null,
selected: ICountry,
) {
setIsValid(valid);
console.log(`${selected.name.common}: ${type}`);
}

return (
<View style={{padding: 24}}>
<PhoneInput
value={phone}
onChangePhoneNumber={setPhone}
country={country}
onChangeCountry={setCountry}
onValidationChange={handleValidation}
/>
{!isValid && phone.length > 0 && (
<Text style={{color: '#dc2626'}}>Invalid phone number</Text>
)}
<Button title="Submit" disabled={!isValid} onPress={() => {}} />
</View>
);
}
note

onValidationChange fires on transitions — when validity actually flips. An input that starts empty produces no initial call, so initialize your own state to false.

An input that starts pre-filled (defaultPhoneNumber, or a controlled value) does get one call on mount with its initial validity — otherwise a form opening on an already valid number would sit on that false forever.

2. Imperative — ref.isValidPhoneNumber

Best when you only care at submit time and do not want a re-render per keystroke.

import React, {useRef} from 'react';
import {Alert, Button, View} from 'react-native';
import PhoneInput, {IPhoneInputRef} from 'rn-international-phone-number';

export default function App() {
const ref = useRef<IPhoneInputRef>(null);

function onSubmit() {
if (!ref.current?.isValidPhoneNumber) {
Alert.alert('Invalid phone number');
return;
}
// E.164, ready for the backend
send(ref.current.internationalPhoneNumber);
}

return (
<View style={{padding: 24}}>
<PhoneInput ref={ref} defaultCountry="US" />
<Button title="Submit" onPress={onSubmit} />
</View>
);
}

ref.isValidPhoneNumber uses the currently selected country automatically.

3. Standalone — isValidPhoneNumber(phoneNumber, country)

Best outside the component: re-validating stored data, a Yup/Zod schema, a script.

import {
getCountryByCca2,
getCountryByPhoneNumber,
isValidPhoneNumber,
} from 'rn-international-phone-number';

// national number + explicit country
isValidPhoneNumber('11912345678', getCountryByCca2('BR')!); // true

// E.164 number, country inferred
const stored = '+12025550123';
const country = getCountryByPhoneNumber(stored);
isValidPhoneNumber(stored, country!); // true

With a schema validator:

import {z} from 'zod';

const schema = z.object({
phoneNumber: z
.string()
.refine((v) => {
const country = getCountryByPhoneNumber(v);
return !!country && isValidPhoneNumber(v, country);
}, 'Invalid phone number'),
});

Detecting the line type

Beyond valid/invalid, the library reports what kind of line a number is — useful to reject landlines when you plan to send an SMS.

<PhoneInput
onPhoneNumberTypeChange={(type) => setIsMobile(type === 'MOBILE')}
/>

Or on submit, via the ref:

const type = ref.current?.phoneNumberType; // 'MOBILE' | 'FIXED_LINE' | null | …

Or standalone, from an E.164 string:

import {getPhoneNumberType} from 'rn-international-phone-number';

getPhoneNumberType('+12025550123'); // 'FIXED_LINE_OR_MOBILE'

PhoneNumberType values

ValueMeaning
MOBILEMobile line
FIXED_LINELandline
FIXED_LINE_OR_MOBILEPlan does not distinguish the two (e.g. US)
TOLL_FREEToll-free number
PREMIUM_RATEPremium-rate number
SHARED_COSTShared-cost number
VOIPVoIP number
PERSONAL_NUMBERPersonal number
PAGERPager
UANUniversal Access Number
VOICEMAILVoicemail access number
nullNumber is incomplete or invalid

:::caution SMS-only flows Many countries — the US included — return FIXED_LINE_OR_MOBILE because their numbering plan does not separate mobile from landline. Rejecting everything that is not exactly MOBILE will lock out real users. Accept FIXED_LINE_OR_MOBILE too. :::

Validating with a headless UI

usePhoneInput exposes the same two values:

const {isValidPhoneNumber, phoneNumberType} = usePhoneInput({defaultCountry: 'BR'});

Next