Skip to content

Examples

Form submission with full phone

Combine callingCode and localPhone to build the full international phone number on submit:

tsx
import { usePhoneNumber } from '@erag/phone-number-react'

export function RegisterForm() {
  const phone = usePhoneNumber({ countryCode: 'IN' })

  const fullPhone = phone.callingCode && phone.localPhone
    ? `${phone.callingCode}${phone.localPhone}`
    : ''

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    if (!phone.isValid) return
    console.log('Submitting:', fullPhone)
    // fetch('/api/register', { method: 'POST', body: JSON.stringify({ phone: fullPhone }) })
  }

  return (
    <form onSubmit={handleSubmit}>
      <select
        value={phone.selectedCountry?.isoCode2 ?? ''}
        onChange={(e) =>
          phone.handleInput(
            phone.countryOptions.find((c) => (c.isoCode2 ?? c.key) === e.target.value) ?? ''
          )
        }
      >
        {phone.countryOptions.map((c) => (
          <option key={c.key} value={c.isoCode2 ?? c.key}>
            {c.isoCode2} — {c.name} (+{c.countryCodes?.[0]})
          </option>
        ))}
      </select>

      <input
        value={phone.localPhone}
        onChange={phone.handleInput}
        inputMode="numeric"
        placeholder="Phone number"
      />

      {phone.localPhone && !phone.isValid && (
        <p style={{ color: 'red' }}>
          Enter a valid phone number for {phone.selectedCountry?.name}
        </p>
      )}

      <button type="submit" disabled={!phone.isValid}>
        Register
      </button>
    </form>
  )
}

With Inertia.js (@inertiajs/react)

tsx
import { useForm } from '@inertiajs/react'
import { usePhoneNumber } from '@erag/phone-number-react'

export function ProfileForm() {
  const form = useForm({ phone: '' })
  const phone = usePhoneNumber()

  function onPhoneChange(e: React.ChangeEvent<HTMLInputElement>) {
    phone.handleInput(e)
    form.setData('phone', `${phone.callingCode}${phone.localPhone}`)
  }

  return (
    <form onSubmit={(e) => { e.preventDefault(); form.post('/profile') }}>
      <select
        value={phone.selectedCountry?.isoCode2 ?? ''}
        onChange={(e) =>
          phone.handleInput(
            phone.countryOptions.find((c) => (c.isoCode2 ?? c.key) === e.target.value) ?? ''
          )
        }
      >
        {phone.countryOptions.map((c) => (
          <option key={c.key} value={c.isoCode2 ?? c.key}>
            {c.isoCode2} — {c.name}
          </option>
        ))}
      </select>

      <input
        value={phone.localPhone}
        onChange={onPhoneChange}
        inputMode="numeric"
        placeholder="Phone number"
      />

      {form.errors.phone && <p>{form.errors.phone}</p>}

      <button type="submit" disabled={form.processing || !phone.isValid}>
        Save
      </button>
    </form>
  )
}

Custom data loaded from an API

Pass a custom data object when countries come from an API. Re-render triggers naturally when state updates:

tsx
import { useState, useEffect } from 'react'
import { usePhoneNumber, type PhoneNumberSharedData } from '@erag/phone-number-react'

export function PhoneWithDynamicData() {
  const [data, setData] = useState<PhoneNumberSharedData>({ countries: [], dialCodes: [] })
  const phone = usePhoneNumber(data)

  useEffect(() => {
    fetch('/api/phone-countries')
      .then((res) => res.json())
      .then(setData)
  }, [])

  return (
    <select
      value={phone.selectedCountry?.isoCode2 ?? ''}
      onChange={(e) =>
        phone.handleInput(
          phone.countryOptions.find((c) => (c.isoCode2 ?? c.key) === e.target.value) ?? ''
        )
      }
    >
      {phone.countryOptions.map((c) => (
        <option key={c.key} value={c.isoCode2 ?? c.key}>
          {c.isoCode2} — {c.name}
        </option>
      ))}
    </select>
  )
}

Styled with Tailwind CSS

tsx
import { usePhoneNumber } from '@erag/phone-number-react'

export function PhoneField() {
  const phone = usePhoneNumber({ countryCode: 'IN' })

  return (
    <div className="flex flex-col gap-2 max-w-sm">
      <label className="text-sm font-medium text-gray-700">Phone Number</label>

      <div className="flex gap-2">
        <select
          value={phone.selectedCountry?.isoCode2 ?? ''}
          onChange={(e) =>
            phone.handleInput(
              phone.countryOptions.find((c) => (c.isoCode2 ?? c.key) === e.target.value) ?? ''
            )
          }
          className="rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-sky-500"
        >
          {phone.countryOptions.map((c) => (
            <option key={c.key} value={c.isoCode2 ?? c.key}>
              {c.isoCode2} +{c.countryCodes?.[0]}
            </option>
          ))}
        </select>

        <input
          value={phone.localPhone}
          onChange={phone.handleInput}
          inputMode="numeric"
          placeholder={phone.mask}
          className={`flex-1 rounded-lg border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-sky-500 ${
            phone.localPhone && !phone.isValid ? 'border-red-400' : 'border-gray-300'
          }`}
        />
      </div>

      <p className="text-xs text-gray-400">
        {phone.callingCode} · {phone.isValid ? '✅ Valid' : 'Enter a valid number'}
      </p>
    </div>
  )
}

MIT License. Copyright Er Amit Gupta