Appearance
Login with email
To authenticate a user via their email address, use the Expo SDK's useLoginWithEmail
hook
tsx
import {useLoginWithEmail} from '@privy-io/expo';
...
const {sendCode, loginWithCode} = useLoginWithEmail();
You can use the returned methods sendCode
and loginWithCode
to authenticate your user per the instructions below.
TIP
After a user has already been authenticated, you can link their email address to an existing account by following the same flow with the useLinkWithEmail
hook instead.
Send an OTP
Send a one-time passcode (OTP) to the user's email by passing their email address to the sendCode
method returned from useLoginWithEmail
:
tsx
import {useLoginWithEmail} from '@privy-io/expo';
export function LoginScreen() {
const [email, setEmail] = useState('');
const {sendCode} = useLoginWithEmail();
return (
<View>
<Text>Login</Text>
<TextInput value={email} onChangeText={setEmail} placeholder="Email" inputMode="email" />
<Button onPress={() => sendCode({email})}>Send Code</Button>
</View>
);
}
If sendCode
succeeds, it will return {success: true}
.
If it fails due to an invalid email address, a network issue, or otherwise, {success: false}
will be returned, and the resulting error can be handled with the onError
callback detailed below.
Authenticate with OTP
The user will then receive an email with a 6-digit OTP. Prompt for this OTP within your application, then authenticate the user with the loginWithCode
method returned from the useLoginWithEmail
hook. As a parameter to this method, pass an object with the following fields:
Field | Type | Description |
---|---|---|
code | string | OTP code inputted by the user in your app. |
email | string | (Optional) The user's email address. Though this parameter is optional, it is highly recommended that you pass the user's email address explicitly. |
Below is an example:
tsx
import {useLoginWithEmail} from '@privy-io/expo';
export function LoginScreen() {
const [code, setCode] = useState('');
const {loginWithCode} = useLoginWithEmail();
return (
<View>
<Text>Login</Text>
<TextInput value={code} onChangeText={setCode} placeholder="Code" inputMode="numeric" />
<Button onPress={() => loginWithCode({code: code, email: 'user@email.com'})}>Login</Button>
</View>
);
}
If loginWithCode
succeeds, it will return a PrivyUser
object with details about the authenticated user.
Reasons loginWithCode
might fail include:
- the network request fails
- the login attempt is made after the user is already logged in
- the OTP
code
has not been either sent, or provided as optional param
To handle these failures, use the onError
callback as described below.
Callbacks
onSendCodeSuccess
Pass an onSendCodeSuccess
function to useLoginWithEmail
to run custom logic after an OTP has been sent. Within this callback you can access the email the code was sent to.
You can use this callback with both the useLoginWithEmail
and useLinkWithEmail
hooks.
tsx
import {useLoginWithEmail} from '@privy-io/expo';
export function LoginScreen() {
const {sendCode, loginWithCode} = useLoginWithEmail({
onSendCodeSuccess({email}) {
// show a toast, send analytics event, etc...
},
});
// ...
}
onLoginSuccess
Pass an onLoginSuccess
function to useLoginWithEmail
to run custom logic after a successful login. Within this callback you can access the PrivyUser
returned by loginWithCode
, as well as an isNewUser
boolean indicating if this is the user's first login to your app.
You may only use the onLoginSuccess
callback with the useLoginWithEmail
hook. For the useLinkWithEmail
hook, you may use the identical onLinkSuccess
callback instead.
tsx
import {useLoginWithEmail} from '@privy-io/expo';
export function LoginScreen() {
const {sendCode, loginWithCode} = useLoginWithEmail({
onLoginSuccess(user, isNewUser) {
// show a toast, send analytics event, etc...
},
});
// ...
}
onError
Pass an onError
function to useLoginWithEmail
to declaratively handle errors occur during the flow.
tsx
import {useLoginWithEmail} from '@privy-io/expo';
export function LoginScreen() {
const {sendCode, loginWithCode} = useLoginWithEmail({
onError(error) {
// show a toast, update form errors, etc...
},
});
// ...
}
Tracking login flow state
The state
variable returned from useLoginWithEmail
will always be one of the following values.
ts
type OtpFlowState =
| {status: 'initial'}
| {status: 'error'; error: Error | null}
| {status: 'sending-code'}
| {status: 'awaiting-code-input'}
| {status: 'submitting-code'}
| {status: 'done'};
Conditional rendering
You can use the state.status
variable to conditionally render your UI based on the user's current state in the login flow.
tsx
import {View, Text, TextInput, Button} from 'react-native';
import {useLoginWithEmail} from '@privy-io/expo';
export function LoginScreen() {
const [code, setCode] = useState('');
const [email, setEmail] = useState('');
const {state, sendCode, loginWithCode} = useLoginWithEmail();
return (
<View>
<View>
<TextInput onChangeText={setEmail} />
<Button
// Keeps button disabled while code is being sent
disabled={state.status === 'sending-code'}
onPress={() => sendCode({email})}
>
<Text>Send Code</Text>
</Button>
{state.status === 'sending-code' && (
// Shows only while the code is sending
<Text>Sending Code...</Text>
)}
</View>
<View>
<TextInput onChangeText={setCode} />
<Button
// Keeps button disabled until the code has been sent
disabled={state.status !== 'awaiting-code-input'}
onPress={() => loginWithCode({code})}
>
<Text>Login</Text>
</Button>
</View>
{state.status === 'submitting-code' && (
// Shows only while the login is being attempted
<Text>Logging in...</Text>
)}
</View>
);
}
Error state
When state.status
is equal to 'error'
, the error value is accessible as state.error
which can be used to render inline hints in a login form.
tsx
import {useLoginWithEmail, hasError} from '@privy-io/expo';
export function LoginScreen() {
const {state, sendCode, loginWithCode} = useLoginWithEmail();
return (
<View>
{/* other ui... */}
{state.status === 'error' && (
<>
<Text style={{color: 'red'}}>There was an error</Text>
<Text style={{color: 'lightred'}}>{state.error.message}</Text>
</>
)}
{hasError(state) && (
// The `hasError` util is also provided as a convenience
// (for typescript users, this provides the same type narrowing as above)
<>
<Text style={{color: 'red'}}>There was an error</Text>
<Text style={{color: 'lightred'}}>{state.error.message}</Text>
</>
)}
</View>
);
}
Enforcing login vs. sign-up
Depending on how your app's authentication flow is set up, you may want to provide separate routes for signing up to your app (e.g. creating their account for the first time) and logging in as a returning user (e.g. logging into an existing account). You can distinguish login vs. sign-up flows by passing an optional disableSignup
boolean to your login call like so:
tsx
<Button
onPress={() =>
loginWithCode({
email,
code,
})
}
>
<Text>Login</Text>
</Button>