react-native-international-phone-number
Version:
International mobile phone input component with mask for React Native
888 lines (745 loc) โข 22.3 kB
Markdown
<br>
<div align = "center">
<img src="lib/assets/images/preview.png" alt="React Native International Phone Number Input Lib preview">
</div>
<br>
<h1 align="center">React Native International Phone Number Input</h1>
<p align="center">
<a href="https://www.npmjs.com/package/react-native-international-phone-number">
<img src="https://img.shields.io/npm/v/react-native-international-phone-number.svg?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/react-native-international-phone-number">
<img src="https://img.shields.io/npm/dt/react-native-international-phone-number.svg?style=flat-square&color=success">
</a>
<a href="https://github.com/AstrOOnauta/react-native-international-phone-number">
<img src="https://img.shields.io/github/stars/AstrOOnauta/react-native-international-phone-number?style=flat-square&color=success"/>
</a>
<a href="https://github.com/AstrOOnauta/react-native-international-phone-number/issues">
<img src="https://img.shields.io/github/issues/AstrOOnauta/react-native-international-phone-number?style=flat-square&color=blue"/>
</a>
<a href="https://github.com/AstrOOnauta/react-native-international-phone-number/pulls">
<img src="https://img.shields.io/github/issues-pr/AstrOOnauta/react-native-international-phone-number?style=flat-square&color=blue"/>
</a>
<a href="LICENSE.md">
<img src="https://img.shields.io/:license-isc-yellow.svg?style=flat-square"/>
</a>
</p>
<br>
<div align = "center">
<a href="https://www.buymeacoffee.com/astroonauta" target="_blank">
<img src="https://survivingmexico.files.wordpress.com/2018/07/button-gif.gif" alt="Buy Me A Coffee" style="height: auto !important;width: 60% !important;">
</a>
</div>
<br>
## Features
- ๐ฑ Works with iOS, Android (Cross-platform), Expo and Web;
- โ
Check phone number validation;
- ๐จ Lib with UI customizable;
- ๐ Phone Input Mask according with the selected country;
- ๐จโ๐ป Functional and class component support;
- ๐ถ 22 languages supported.
<br>
## Try it out
- [Demo](https://snack.expo.dev/@astroonauta/react-native-international-phone-number)
<br>
## List of Contents
- [Old Versions](#old-versions)
- [Installation](#installation)
- [Additional Config](#additional-config)
- [React Native CLI](#using-react-native-cli)
- [Expo](#using-expo)
- [Web](#using-web)
- [Basic Usage](#basic-usage)
- [With Class Component](#class-component)
- [With Function Component](#function-component)
- [With Typescript](#typescript)
- [Intermediate Usage](#intermediate-usage)
- [Typescript + useRef](#typescript--useref)
- [Advanced Usage](#advanced-usage)
- [React-Hook-Form + Typescript + Default Phone Number Value](#react-hook-form--typescript--default-phone-number-value)
- [Customizing Lib](#customizing-lib)
- [Dark Mode](#dark-mode)
- [Custom Lib Styles](#custom-lib-styles)
- [Custom Caret](#custom-caret)
- [Custom Placeholders/Messages](#custom-placeholdersmessages)
- [Custom Modal Height](#custom-modal-height)
- [Country Modal Disabled Mode](#country-modal-disabled-mode)
- [Phone Input Disabled Mode](#phone-input-disabled-mode)
- [Custom Disabled Mode Style](#custom-disabled-mode-style)
- [Change Default Language](#change-default-language)
- [Custom Phone Mask](#custom-phone-mask)
- [Custom Default Flag/Country](#custom-default-flagcountry)
- [Default Phone Number Value](#default-phone-number-value)
- [Show Only Some Countries Inside Modal](#show-only-some-countries)
- [Exclude some countries Inside Modal](#exclude-some-countries)
- [Show Popular Countries at the Top of the Countries List Inside Modal](#show-popular-countries-at-the-top-of-the-countries-list-inside-modal)
- [Custom Modal Section Titles](#custom-modal-section-titles)
- [Hide the Modal Section Titles](#hide-the-modal-section-titles)
- [Right to Left Input](#right-to-left-input)
- [Dont allow Zero After Calling Code](#dont-allow-zero-after-calling-code)
- [Lib Props](#component-props-phoneinputprops)
- [Lib Functions](#functions)
- [Supported languages](#๐-supported-languages-๐)
- [Testing](#testing)
- [Contributing](#contributing)
- [License](#license)
<br>
## Old Versions
- [Version 0.8.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.8.x)
- [Version 0.7.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.7.x)
- [Version 0.6.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.6.x)
- [Version 0.5.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.5.x)
- [Version 0.4.x](https://github.com/AstrOOnauta/react-native-international-phone-number/tree/v0.4.x)
<br>
## Installation
```bash
$ npm i --save react-native-international-phone-number
```
OR
```bash
$ yarn add react-native-international-phone-number
```
## Additional config
- ### Using React Native CLI:
Create a `react-native.config.js` file at the root of your react-native project with:
```bash
module.exports = {
project: {
ios: {},
android: {},
},
assets: [
'./node_modules/react-native-international-phone-number/lib/assets/fonts',
],
};
```
Then link the font to your native projects with:
```bash
npx react-native-asset
```
- ### Using Expo:
1. Install [expo-fonts](https://docs.expo.dev/versions/latest/sdk/font/): `npx expo install expo-font`;
2. Initialize the `expo-font`:
```bash
import { useFonts } from 'expo-font';
...
useFonts({
'TwemojiMozilla': require('./node_modules/react-native-international-phone-number/lib/assets/fonts/TwemojiMozilla.woff2'),
});
...
```
> Observation: you need to recompile your project after adding new fonts.
<br>
## Basic Usage
- ### Class Component:
```jsx
import React from 'react';
import { View, Text } from 'react-native';
import PhoneInput, { isValidPhoneNumber } from 'react-native-international-phone-number';
export class App extends React.Component {
constructor(props) {
super(props)
this.state = {
selectedCountry: null,
inputValue: ''
}
}
function handleSelectedCountry(country) {
this.setState({
selectedCountry: country
})
}
function handleInputValue(phoneNumber) {
this.setState({
inputValue: phoneNumber
})
}
render(){
return (
<View style={{ width: '100%', flex: 1, padding: 24 }}>
<PhoneInput
value={this.state.inputValue}
onChangePhoneNumber={this.handleInputValue}
selectedCountry={this.state.selectedCountry}
onChangeSelectedCountry={this.handleSelectedCountry}
/>
<View style={{ marginTop: 10 }}>
<Text>
Country:{' '}
{`${this.state.selectedCountry?.name?.en} (${this.state.selectedCountry?.cca2})`}
</Text>
<Text>
Phone Number: {`${this.state.selectedCountry?.callingCode} ${this.state.inputValue}`}
</Text>
<Text>
isValid:{' '}
{isValidPhoneNumber(this.state.inputValue, this.state.selectedCountry)
? 'true'
: 'false'}
</Text>
</View>
</View>
);
}
}
```
- ### Function Component:
```jsx
import React, { useState } from 'react';
import { View, Text } from 'react-native';
import PhoneInput, {
isValidPhoneNumber,
} from 'react-native-international-phone-number';
export default function App() {
const [selectedCountry, setSelectedCountry] = useState(null);
const [inputValue, setInputValue] = useState('');
function handleInputValue(phoneNumber) {
setInputValue(phoneNumber);
}
function handleSelectedCountry(country) {
setSelectedCountry(country);
}
return (
<View style={{ width: '100%', flex: 1, padding: 24 }}>
<PhoneInput
value={inputValue}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
/>
<View style={{ marginTop: 10 }}>
<Text>
Country:{' '}
{`${selectedCountry?.name?.en} (${selectedCountry?.cca2})`}
</Text>
<Text>
Phone Number:{' '}
{`${selectedCountry?.callingCode} ${inputValue}`}
</Text>
<Text>
isValid:{' '}
{isValidPhoneNumber(inputValue, selectedCountry)
? 'true'
: 'false'}
</Text>
</View>
</View>
);
}
```
- ### Typescript
```tsx
import React, { useState } from 'react';
import { View, Text } from 'react-native';
import PhoneInput, {
ICountry,
isValidPhoneNumber,
} from 'react-native-international-phone-number';
export default function App() {
const [selectedCountry, setSelectedCountry] =
useState<null | ICountry>(null);
const [inputValue, setInputValue] = useState<string>('');
function handleInputValue(phoneNumber: string) {
setInputValue(phoneNumber);
}
function handleSelectedCountry(country: ICountry) {
setSelectedCountry(country);
}
return (
<View style={{ width: '100%', flex: 1, padding: 24 }}>
<PhoneInput
value={inputValue}
onChangePhoneNumber={handleInputValue}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
/>
<View style={{ marginTop: 10 }}>
<Text>
Country:{' '}
{`${selectedCountry?.name?.en} (${selectedCountry?.cca2})`}
</Text>
<Text>
Phone Number:{' '}
{`${selectedCountry?.callingCode} ${inputValue}`}
</Text>
<Text>
isValid:{' '}
{isValidPhoneNumber(inputValue, selectedCountry)
? 'true'
: 'false'}
</Text>
</View>
</View>
);
}
```
<br>
## Intermediate Usage
- ### Typescript + useRef
```tsx
import React, { useRef } from 'react';
import { View, Text } from 'react-native';
import PhoneInput, {
ICountry,
IPhoneInputRef,
} from 'react-native-international-phone-number';
export default function App() {
const phoneInputRef = useRef<IPhoneInputRef>(null);
function onSubmitRef() {
Alert.alert(
'Intermediate Result',
`Country: ${inputRef.current?.selectedCountry?.name?.en} \nPhone Number: ${inputRef.current?.fullPhoneNumber} \nisValid: ${inputRef.current?.isValid}`
);
}
return (
<View style={{ width: '100%', flex: 1, padding: 24 }}>
<PhoneInput ref={phoneInputRef} />
<TouchableOpacity
style={{
width: '100%',
paddingVertical: 12,
backgroundColor: '#2196F3',
borderRadius: 4,
marginTop: 10,
}}
onPress={onSubmit}
>
<Text
style={{
color: '#F3F3F3',
textAlign: 'center',
fontSize: 16,
fontWeight: 'bold',
}}
>
Submit
</Text>
</TouchableOpacity>
</View>
);
}
```
> Observation: Don't use the useRef hook combined with the useState hook to manage the phoneNumber and selectedCountry values. Instead, choose to use just one of them (useRef or useState).
<br>
## Advanced Usage
- ### React-Hook-Form + Typescript + Default Phone Number Value
```tsx
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, Alert } from 'react-native';
import PhoneInput, {
ICountry,
isValidPhoneNumber,
} from 'react-native-international-phone-number';
import { Controller, FieldValues } from 'react-hook-form';
interface FormProps extends FieldValues {
phoneNumber: string;
}
export default function App() {
const [selectedCountry, setSelectedCountry] = useState<
undefined | ICountry
>(undefined);
function handleSelectedCountry(country: ICountry) {
setSelectedCountry(country);
}
function onSubmit(form: FormProps) {
const phoneNumber = `${selectedCountry?.callingCode} ${form.phoneNumber}`;
const isValid = isValidPhoneNumber(
form.phoneNumber,
selectedCountry as ICountry
);
Alert.alert(
'Advanced Result',
`Country: ${selectedCountry?.name?.en} \nPhone Number: ${phoneNumber} \nisValid: ${isValid}`
);
}
return (
<View style={{ width: '100%', flex: 1, padding: 24 }}>
<Controller
name="phoneNumber"
control={control}
render={({ field: { onChange, value } }) => (
<PhoneInput
defaultValue="+12505550199"
value={value}
onChangePhoneNumber={onChange}
selectedCountry={selectedCountry}
onChangeSelectedCountry={handleSelectedCountry}
/>
)}
/>
<TouchableOpacity
style={{
width: '100%',
paddingVertical: 12,
backgroundColor: '#2196F3',
borderRadius: 4,
}}
onPress={handleSubmit(onSubmit)}
>
<Text
style={{
color: '#F3F3F3',
textAlign: 'center',
fontSize: 16,
fontWeight: 'bold',
}}
>
Submit
</Text>
</TouchableOpacity>
</View>
);
}
```
> Observations:
>
> 1. You need to use a default value with the following format: `+(country callling code)(area code)(number phone)`
> 2. The lib has the mechanism to set the flag and mask of the supplied `defaultValue`. However, if the supplied `defaultValue` does not match any international standard, the `input mask of the defaultValue` will be set to "BR" (please make sure that the default value is in the format mentioned above).
<br>
## Customizing lib
<img src="lib/assets/images/custom-styles.png" alt="Custom lib styles">
- ### Dark Mode:
```jsx
...
<PhoneInput
...
theme="dark"
/>
...
```
- ### Custom Lib Styles:
```jsx
...
<PhoneInput
...
phoneInputStyles={{
container: {
backgroundColor: '#575757',
borderWidth: 1,
borderStyle: 'solid',
borderColor: '#F3F3F3',
},
flagContainer: {
borderTopLeftRadius: 7,
borderBottomLeftRadius: 7,
backgroundColor: '#808080',
justifyContent: 'center',
},
flag: {},
caret: {
color: '#F3F3F3',
fontSize: 16,
},
divider: {
backgroundColor: '#F3F3F3',
}
callingCode: {
fontSize: 16,
fontWeight: 'bold',
color: '#F3F3F3',
},
input: {
color: '#F3F3F3',
},
}}
modalStyles={{
modal: {
backgroundColor: '#333333',
borderWidth: 1,
},
backdrop: {},
divider: {
backgroundColor: 'transparent',
},
countriesList: {},
searchInput: {
borderRadius: 8,
borderWidth: 1,
borderColor: '#F3F3F3',
color: '#F3F3F3',
backgroundColor: '#333333',
paddingHorizontal: 12,
height: 46,
},
countryButton: {
borderWidth: 1,
borderColor: '#F3F3F3',
backgroundColor: '#666666',
marginVertical: 4,
paddingVertical: 0,
},
noCountryText: {},
noCountryContainer: {},
flag: {
color: '#FFFFFF',
fontSize: 20,
},
callingCode: {
color: '#F3F3F3',
},
countryName: {
color: '#F3F3F3',
},
sectionTitle: {
marginVertical: 10,
color: '#F3F3F3',
}
}}
/>
...
```
- ### Custom Caret:
```jsx
...
<PhoneInput
...
customCaret={<Icon name="chevron-down" size={30} color="#000000" />} // react-native-vector-icons
/>
...
```
- ### Custom Placeholders/Messages:
```jsx
...
<PhoneInput
...
placeholder="Custom Phone Input Placeholder"
modalSearchInputPlaceholder="Custom Modal Search Input Placeholder"
modalNotFoundCountryMessage="Custom Modal Not Found Country Message"
/>
...
```
- ### Custom Modal Height:
```jsx
...
<PhoneInput
...
modalHeight="80%"
/>
...
```
- ### Country Modal Disabled Mode:
```jsx
...
<PhoneInput
...
modalDisabled
/>
...
```
- ### Phone Input Disabled Mode:
```jsx
...
<PhoneInput
...
disabled
/>
...
```
- ### Custom Disabled Mode Style:
```jsx
const [isDisabled, setIsDisabled] = useState<boolean>(true)
...
<PhoneInput
...
containerStyle={ isDisabled ? { backgroundColor: 'yellow' } : {} }
disabled={isDisabled}
/>
...
```
- ### Change Default Language:
```jsx
...
<PhoneInput
...
language="pt"
/>
...
```
- ### Custom Phone Mask:
```jsx
...
<PhoneInput
...
customMask={['#### ####', '##### ####']}
/>
...
```
- ### Custom Default Flag/Country:
```jsx
...
<PhoneInput
...
defaultCountry="CA"
/>
...
```
- ### Default Phone Number Value:
```jsx
...
<PhoneInput
...
defaultValue="+12505550199"
/>
...
```
> Observations:
>
> 1. You need to use a default value with the [e164](https://en.wikipedia.org/wiki/E.164) format: `+(country callling code)(area code)(number phone)`
> 2. The lib has the mechanism to set the flag and mask of the supplied `defaultValue`. However, if the supplied `defaultValue` does not match any international standard, the `input mask of the defaultValue` will be set to "BR" (please make sure that the default value is in the format mentioned above).
- ### Show Only Some Countries Inside Modal:
```jsx
...
<PhoneInput
...
showOnly={['BR', 'PT', 'CA', 'US']}
/>
...
```
- ### Exclude Some Countries Inside Modal:
```jsx
...
<PhoneInput
...
excludedCountries={['BR', 'PT', 'CA', 'US']}
/>
...
```
- ### Show Popular Countries at the Top of the Countries List Inside Modal:
```jsx
...
<PhoneInput
...
popularCountriess={['BR', 'PT', 'CA', 'US']}
/>
...
```
- ### Custom Modal Section Titles:
```jsx
...
<PhoneInput
...
popularCountriess={['BR', 'PT', 'CA', 'US']}
popularCountriesSectionTitle='Suggested'
restOfCountriesSectionTitle='All'
/>
...
```
- ### Hide the Modal Section Titles:
```jsx
...
<PhoneInput
...
popularCountriess={['BR', 'PT', 'CA', 'US']}
modalSectionTitleDisabled
/>
...
```
- ### Right to Left Input:
```jsx
import { I18nManager } from "react-native";
...
<PhoneInput
...
rtl={I18nManager.isRTL}
/>
...
```
- ### Don't allow Zero After Calling Code:
```jsx
...
<PhoneInput
...
allowZeroAfterCallingCode={false}
/>
...
```
</br>
## Component Props ([PhoneInputProps](lib/interfaces/phoneInputProps.ts))
- `language?:` [ILanguage](lib/interfaces/language.ts);
- `customMask?:` string[];
- `defaultValue?:` string;
- `value?:` string;
- `onChangePhoneNumber?:` (phoneNumber: string) => void;
- `defaultCountry?:` [ICountryCca2](lib/interfaces/countryCca2.ts);
- `selectedCountry?:` [ICountry](lib/interfaces/country.ts);
- `onChangeSelectedCountry?:` (country: [ICountry](lib/interfaces/country.ts)) => void;
- `showOnly?:` [ICountryCca2[]](lib/interfaces/countryCca2.ts);
- `excludedCountries?:` [ICountryCca2[]](lib/interfaces/countryCca2.ts);
- `popularCountries?:` [ICountryCca2[]](lib/interfaces/countryCca2.ts);
- `popularCountriesSectionTitle?:` string;
- `restOfCountriesSectionTitle?:` string;
- `modalSectionTitleDisabled?:` boolean;
- `rtl?:` boolean;
- `disabled?:` boolean;
- `modalDisabled?:` boolean;
- `modalHeight?:` number | string;
- `theme?:` [ITheme](lib/interfaces/theme.ts);
- `phoneInputStyles?:` [IPhoneInputStyles](lib/interfaces/phoneInputStyles.ts);
- `modalStyles?:` [IModalStyles](lib/interfaces/modalStyles.ts);
- `modalSearchInputPlaceholder?:` string;
- `modalSearchInputPlaceholderTextColor?:` string;
- `modalSearchInputSelectionColor?:` string;
- `modalNotFoundCountryMessage?:` string;
- `customCaret?:` [ReactNode](https://reactnative.dev/docs/react-node);
- `ref?:` [Ref](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/663f439d11d78b65f1dfd38d120f3728ea2cc207/types/react/index.d.ts#L100)<[IPhoneInputRef](lib/interfaces/phoneInputRef.ts)>;
- `allowZeroAfterCallingCode?:` boolean.
<br>
## Functions
- `getAllCountries:` () => [ICountry](lib/interfaces/country.ts)[];
- `getCountriesByCallingCode:` (callingCode: string) => [ICountry](lib/interfaces/country.ts)[] | undefined;
- `getCountryByCca2:` (cca2: string) => [ICountry](lib/interfaces/country.ts) | undefined;
- `getCountriesByName:` (name: string, language: [ILanguage](lib/interfaces/language.ts)) => [ICountry](lib/interfaces/country.ts)[] | undefined;
- `getCountryByPhoneNumber:` (phoneNumber: string) => [ICountry](lib/interfaces/country.ts) | undefined;
- `isValidPhoneNumber:` (phoneNumber: string, country: [ICountry](lib/interfaces/country.ts)) => boolean.
</br>
## ๐ Supported languages ๐
```js
"name": {
"bg": "Bulgarian",
"by": "Belarusian",
"cn": "Chinese",
"cz": "Czech",
"de": "German",
"ee": "Estonian",
"el": "Greek",
"en": "English",
"es": "Espanol",
"fr": "French",
"he": "Hebrew",
"it": "Italian",
"jp": "Japanese",
"nl": "Dutch",
"pl": "Polish",
"pt": "Portuguese",
"ro": "Romanian",
"ru": "Russian",
"ua": "Ukrainian",
"zh": "Chinese (Simplified)",
"ar": "Arabic",
"tr": "Turkish"
},
```
<br>
## Testing
When utilizing this package, you may need to target the PhoneInput component in your automated tests. To facilitate this, we provide a testID props for the PhoneInput component. The testID can be integrated with popular testing libraries such as @testing-library/react-native or Maestro. This enables you to efficiently locate and interact with PhoneInput elements within your tests, ensuring a robust and reliable testing experience.
```jsx
// Assuming PhoneInput has testID="phone-number"
const phoneInput = getByTestId('phone-number-flag');
```
<br>
## Contributing
- Fork or clone this repository
```bash
$ git clone https://github.com/AstrOOnauta/react-native-international-phone-number.git
```
- Repair, Update and Enjoy ๐ ๏ธ๐งโ๏ธ
- Create a new PR to this repository
<br>
## License
[ISC](LICENSE.md)
<br>
<div align = "center">
<br>
Thanks for stopping by! ๐
<br>
</div>