ReactNative Expo File Based Routing with Firebase Authentication

ReactNative Expo File Based Routing with Firebase Authentication

ยท

4 min read

Expo Router brings the best routing concepts from the web to native iOS and Android apps. Every file in the app directory automatically becomes a route in your mobile navigation, making it easier than ever to build, maintain, and scale your project. expo.github.io/router/docs

Expo File Based Routing ( Part 1 )

The first part of working with Expo Router and File Based Navigation is covered in the video below

Firebase Authentication ( Part 2 )

The second part has the steps listed below with a video walking through the whole project in GitHub. Source code for both videos in included in the GitHub link at the end of the post.

Setup

npx expo install firebase
npx expo install @react-native-async-storage/async-storage

The rest is based on the directions provide by Expo Firebase Integration Documentation

Create the firebase configuration file

Needed to add persistence to get React Native to remember the previous logged in user; this is why we added the react-native async-storage library.

// firebase-config.js 

const firebaseConfig = { ... }

export const app = initializeApp(firebaseConfig);
export const auth = initializeAuth(app, {
    persistence : getReactNativePersistence(AsyncStorage)
})

We now export the auth object and use that throughout the application instead of calling getAuth in.

We want the app to remember logged in users so we need to add additional code. To check for an authenticated user we need to call onAuthStateChange when app launches and wait for a response before rendering the proper Navigation Stack.

In our store, we have added the user and the initialized properties to the application state. After the onAuthStateChange is triggered we update the store if there is a user, and we indicate that the application is now initialized. We can access the updated values through the store use those values in index.js

// store.js

export const AuthStore = new Store({
  isLoggedIn: false,
  initialized: false,
  user: null,
});


const unsub = onAuthStateChanged(auth, (user) => {
  console.log("onAuthStateChange", user);
  AuthStore.update((store) => {
    store.user = user;
    store.isLoggedIn = user ? true : false;
    store.initialized = true;
  });

});

In index.js we wait for initialization to complete and then we take the appropriate action to redirect the user to the login route if there is no user or the tabs route if a user exists.

const { initialized, isLoggedIn } = AuthStore.useState();

  React.useEffect(() => {
    if (!navigationState?.key || !initialized) return;

    const inAuthGroup = segments[0] === "(auth)";

    if (
      // If the user is not signed in and the initial segment is not anything
      //  segment is not anything in the auth group.
      !isLoggedIn &&
      !inAuthGroup
    ) {
      // Redirect to the login page.
      router.replace("/login");
    } else if (isLoggedIn) {
      // go to tabs root.
      router.replace("/(tabs)/home");
    }
  }, [segments, navigationState?.key, initialized]);

We wrap the firebase calls so we can update the store when appropriate. Below is the full listing of the calls for login, logout and create user account.


export const appSignIn = async (email, password) => {
  try {
    const resp = await signInWithEmailAndPassword(auth, email, password);
    AuthStore.update((store) => {
      store.user = resp.user;
      store.isLoggedIn = resp.user ? true : false;
    });
    return { user: auth.currentUser };
  } catch (e) {
    return { error: e };
  }
};

export const appSignOut = async () => {
  try {
    await signOut(auth);
    AuthStore.update((store) => {
      store.user = null;
      store.isLoggedIn = false;
    });
    return { user: null };
  } catch (e) {
    return { error: e };
  }
};

export const appSignUp = async (email, password, displayName) => {
  try {
    // this will trigger onAuthStateChange to update the store..
    const resp = await createUserWithEmailAndPassword(auth, email, password);

    // add the displayName
    await updateProfile(resp.user, { displayName });

    AuthStore.update((store) => {
      store.user = auth.currentUser;
      store.isLoggedIn = true;
    });

    return { user: auth.currentUser };
  } catch (e) {
    return { error: e };
  }
};

Video ( Part 2 )

Source Code

Firebase integration is in a separate branch in the repo

ย