Build a custom flow for adding a phone number to a user's account
Users are able to add multiple phone numbers to their account.
Every user has a User
object that represents their account. The User
object has a phoneNumbers
property that contains all the phone numbers associated with the user.
The following example demonstrates how to build a custom user interface that allows users to add a phone number to their account. The example:
- Uses the
useUser()
hook to get theUser
object. - Uses the
User.createPhoneNumber()
method to add the phone number to the user's account.
- A new
PhoneNumber
object is created and stored inUser.phoneNumbers
.
- Uses the
prepareVerification()
method on the newly createdPhoneNumber
object to send a verification code to the user. - Uses the
attemptVerification()
method on the samePhoneNumber
object with the verification code provided by the user to verify the phone number.
'use client';
import * as React from 'react';
import { useUser } from '@clerk/nextjs';
import { PhoneNumberResource } from '@clerk/types';
export default function Page() {
const { isLoaded, user } = useUser();
const [phone, setPhone] = React.useState('');
const [code, setCode] = React.useState('');
const [isVerifying, setIsVerifying] = React.useState(false);
const [successful, setSuccessful] = React.useState(false);
const [phoneObj, setPhoneObj] = React.useState<
PhoneNumberResource | undefined
>();
if (!isLoaded) return null;
if (isLoaded && !user?.id) {
return <p>You must be logged in to access this page</p>;
}
// Handle addition of the phone number
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
// Add unverified phone number to user
const res = await user.createPhoneNumber({ phoneNumber: phone });
// Reload user to get updated User object
await user.reload();
// Create a reference to the new phone number to use related methods
const phoneNumber = user.phoneNumbers.find((a) => a.id === res.id);
setPhoneObj(phoneNumber);
// Send the user an SMS with the verification code
phoneNumber?.prepareVerification();
// Set to true to display second form
// and capture the OTP code
setIsVerifying(true);
} catch (err) {
// See https://clerk.com/docs/custom-flows/error-handling
// for more info on error handling
console.error(JSON.stringify(err, null, 2));
}
};
// Handle the submission of the verification form
const verifyCode = async (e: React.FormEvent) => {
e.preventDefault();
try {
// Verify that the provided code matches the code sent to the user
const phoneVerifyAttempt = await phoneObj?.attemptVerification({ code });
if (phoneVerifyAttempt?.verification.status === 'verified') {
setSuccessful(true);
} else {
// If the status is not complete, check why. User may need to
// complete further steps.
console.error(JSON.stringify(phoneVerifyAttempt, null, 2));
}
} catch (err) {
console.error(JSON.stringify(err, null, 2));
}
};
// Display a success message if the phone number was added successfully
if (successful) {
return (
<>
<h1>Phone added</h1>
</>
);
}
// Display the verification form to capture the OTP code
if (isVerifying) {
return (
<>
<h1>Verify phone</h1>
<div>
<form onSubmit={(e) => verifyCode(e)}>
<div>
<label htmlFor="code">Enter code</label>
<input
onChange={(e) => setCode(e.target.value)}
id="code"
name="code"
type="text"
value={code}
/>
</div>
<div>
<button type="submit">Verify</button>
</div>
</form>
</div>
</>
);
}
// Display the initial form to capture the phone number
return (
<>
<h1>Add phone</h1>
<div>
<form onSubmit={(e) => handleSubmit(e)}>
<div>
<label htmlFor="phone">Enter phone number</label>
<input
onChange={(e) => setPhone(e.target.value)}
id="phone"
name="phone"
type="phone"
value={phone}
/>
</div>
<div>
<button type="submit">Continue</button>
</div>
</form>
</div>
</>
);
}