Make sure you've already completed the setup guide, installation & have deployed your app before continuing.
TLDR
Add a userExistsInDb state variable.
Add onAuthStateChanged prop to DoormanProvider. This function gets called whenever the user signs in or out.
Inside this function, call your DB to see if user exists already, and if not, send to the onboarding flow.
Call setUserExistsInDb to update the state based on the DB's response.
When userExistsInDb is false, render an Onboarding screen that lets users enter information, and then adds them to the database. This goes inside of AuthGate.
Call setUserExistsInDb(true) when a user is successfully added to the DB from the Onboarding screen to update the state.
File overview
We're going to create 3 files. AuthLogic and Onboarding contain code distinct to this tutorial.
App.tsx initialize Firebase, render app
AuthLogic.tsx initialize Doorman, and conditionally render an authentication flow, an onboarding screen, or an authenticated app.
Onboarding.tsx Collect user information if they aren't in the DB yet.
1. Initialize Firebase in App
App.tsx
import AuthLogic from './AuthLogic'
import firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore' // <- if you use Firestore
if (!firebase.apps.length) {
// you can replace this with your firebase config
firebase.initializeApp({
apiKey: 'AIzaSyCn8HyP1tVZiagk-YvZRwjSwKdwQw5Pvng',
authDomain: 'tester-9d8bb.firebaseapp.com',
databaseURL: 'https://tester-9d8bb.firebaseio.com',
projectId: 'tester-9d8bb',
storageBucket: 'tester-9d8bb.appspot.com',
messagingSenderId: '760778283392',
appId: '1:760778283392:web:05cb35d0837c93c6584965',
})
}
export default AuthLogic
This is simple boilerplate code that is no different than normal initialization for Firebase/Doorman.
You can replace the firebase.initializeApp config variable with your own firebase config.
2. Initialize Doorman in AuthLogic
This tutorial will rely on the DoormanProvider implementation instead of withPhoneAuth. (They both achieve the same thing, as mentioned in the quick example.)
AuthGate accepts a single child: a function that returns a component. It receives the user and loading fields as arguments.
<AuthGate>
{({ user, loading }) => {
// if loading auth listener
if (loading) return <></>
// if user is not authenticated
if (!user) return <AuthFlow />
// your actual app component, which appears after
// the user has authenticated
return <AfterAuth />
}}
</AuthGate>
Whoa, where did AuthFlow come from? That's the Doorman component that lets your users sign in with phone number. AfterAuth is your normal app component.
Our file now looks like this:
AuthLogic.tsx (or .js if you don't use TypeScript)
import React from 'react'
import { DoormanProvider, AuthGate, AuthFlow } from 'react-native-doorman'
const AuthLogic = () => {
return (
<DoormanProvider publicProjectId="djzlPQFxxzJikNQgLwxN">
<AuthGate>
{({ user, loading }) => {
// if loading auth listener
if (loading) return <></>
// if user is not authenticated
if (!user) return <AuthFlow />
// your actual app component, which appears after
// the user has authenticated
return <AfterAuth />
}}
</AuthGate>
</DoormanProvider>
)
}
export default AuthLogic
2.2 Add user exists in DB logic
Now, on to the code that lets you add your user to the DB.
First, let's add a userExistsInDb state variable, whose initial value is false. Add a checkingIfUserExists state variable too, which indicates if the DB check is loading or not.
Next, add a listener callback to the DoormanProvider component using its onAuthStateChanged prop.
const AuthLogic = () => {
const [userExistsInDb, setUserExistsInDb] = useState(false)
const [checkingIfUserExists, setCheckingIfUserExists] = useState(false)
const onAuthStateChanged = async (user) => {
if (user) {
const { uid } = user
setCheckingIfUserExists(true)
// user your own function here that calls your database to check
const exists = await checkIfUserExistsInDb(uid)
// if you're using Firestore, it might look like this:
const { exists } = await db.doc(`users/${uid}`).get()
// update the state based on our DB value
setUserExistsInDb(exists)
setCheckingIfUserExists(false)
} else {
// user is not signed in, reset the state to false
setUserExists(false)
setCheckingIfUserExists(false)
}
}
return (
<DoormanProvider
publicProjectId="djzlPQFxxzJikNQgLwxN"
onAuthStateChanged={onAuthStateChanged}
>
...
</DoormanProvider>
)
}
The onAuthStateChanged prop is a function that gets called whenever the user signs in or out. It follows the same API from firebase.
2.3 Render Onboarding
The final step is to render an Onboarding screen if userExistsInDb is false.
// 👇 We'll create this in the final step
// you can import your own, too!
import Onboarding from './Onboarding'
...
<AuthGate>
{({ user, loading }) => {
// add checkingIfUserExists to loading condition
if (loading || checkingIfUserExists) return <></>
if (!user) return <AuthFlow />
// If user is authenticated, but doesn't exist in the DB,
// render the onboarding flow.
// We pass it a callback function called onUserAddedToDb
// this function will set userExistsInDb to true
// it gets called after the DB has been updated by <Onboarding />
if (!userExistsInDb) {
return (
<Onboarding
onUserAddedToDb={() => setUserExistsInDb(true)}
/>
)
}
// your actual app component, which appears after
// the user has authenticated
return <AfterAuth />
}}
</AuthGate>
🎉That's it! All that's left is to make the Onboarding screen.
Here is our final AuthLogic screen, in its entirety:
import React, { useState } from 'react'
import { DoormanProvider, AuthGate, AuthFlow } from 'react-native-doorman'
import Onboarding from './Onboarding'
import AfterAuth from './AfterAuth'
import { db } from './db'
const AuthLogic = () => {
const [userExistsInDb, setUserExistsInDb] = useState(false)
const [checkingIfUserExists, setCheckingIfUserExists] = useState(false)
return (
<DoormanProvider
onAuthStateChanged={async user => {
if (user) {
const { uid } = user
setCheckingIfUserExists(true)
// 👇 user your own function here that calls your database to check
// const exists = await checkIfUserExistsInDb(uid)
// if you're using Firestore, it might look like this:
const { exists } = await db.doc(`users/${uid}`).get()
// update the state based on our DB value
setUserExistsInDb(exists)
setCheckingIfUserExists(false)
} else {
// user is not signed in, reset the state to false
setUserExistsInDb(false)
setCheckingIfUserExists(false)
}
}}
publicProjectId="djzlPQFxxzJikNQgLwxN"
>
<AuthGate>
{({ user, loading }) => {
// add checkingIfUserExists to loading condition
if (loading || checkingIfUserExists) return <></>
if (!user) return <AuthFlow />
// If user is authenticated, but doesn't exist in the DB,
// render the onboarding flow.
// We pass it a callback function called onUserAddedToDb
// this function will set userExistsInDb to true
// it gets called after the DB has been updated by <Onboarding />
if (!userExistsInDb) {
return (
<Onboarding onUserAddedToDb={() => setUserExistsInDb(true)} />
)
}
// your actual app component, which appears after
// the user has authenticated
return <AfterAuth />
}}
</AuthGate>
</DoormanProvider>
)
}
export default AuthLogic
Note: If you have your own Onboarding screen (or React Navigation stack, you can render it in place of Onboarding.
If you're using a React Navigation Stack in place of Onboarding (assuming it's with v5), then pass the onUserAddedToDb function as a param to your stack, and call it from your screen using useRoute().params.onUserAddedToDb.
3. Create <Onboarding /> screen
👋Don't forget to call the onUserAddedToDb function after successfully adding your user to the database, as seen in line 19 below! If you don't call it, the app won't re-render.
Here's an example of a basic Onboarding screen:
Onboarding.tsx
import React, { useState } from 'react'
import { TextInput, Button, View } from 'react-native'
import { useDoormanUser } from 'react-native-doorman'
const Onboarding = ({ onUserAddedToDb }) => {
const [name, setName] = useState('')
const { uid } = useDoormanUser()
const handleSubmit = async () => {
// Implement your own DB function here
await addUserToDb({ uid, name })
// ...if you're using Firestore, it might look like:
await db.doc(`users/${uid}`).set({ name }, { merge: true })
// Finally, once the user has been added,
// call the callback function from the props.
// 🚨if you forget to call this function, the app won't re-render
onUserAddedToDb()
}
return (
<View style={styles.container}>
<TextInput
placeholder="Enter your name"
value={name}
onChangeText={setName}
/>
<Button
title="Submit name"
onPress={handleSubmit}
/>
</View>
)
}
export default Onboarding
const styles= StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center'
}
})
4. (Bonus) Create <AfterAuth />
Here's an example app screen, which you can make in your AfterAuth.tsx file:
import React from 'react'
import {
Page,
ScreenBackground,
useDoormanUser,
H1,
Paragraph,
} from 'react-native-doorman'
import { Button } from 'react-native'
const AfterAuth = () => {
const { uid, signOut } = useDoormanUser()
return (
<Page
style={{ marginTop: 100, alignItems: 'center' }}
background={() => <ScreenBackground />}
>
<H1 style={{ color: 'white' }}>Welcome to Doorman.</H1>
<Paragraph style={{ color: 'white' }}>
Sign out below, if {`you'd`} like. Your user id is {uid}.
</Paragraph>
<Button title="Sign Out" color="white" onPress={signOut} />
</Page>
)
}
export default AfterAuth
5. If you're using Firestore
If you're using Firestore as your DB, you'll find examples in this tutorial for how to read and write a user.
For the sake of the tutorial, you can create a db.ts file, and populate it with this. You can also set a custom firebaseConfig from your own Firebase project.