B_M
B_M

Reputation: 173

How do I implement firebase auth in react navigation 5 auth flow?

I'm trying to add firebase auth for sign up, sign in and sign out in my app. I followed the code structure from the react-navigation website. This is the context file...

import React from 'react';

export const AuthContext = React.createContext();

and this is my App file. For the sign-in, I'm using email and password. For the sign-up, I want to add an email, username, and password.

import React, { useContext, useState, useReducer, useEffect, useMemo } from 'react';
import { Home, Profile, Settings, Chat, Phone } from "./src/screens";
import {
  NavigationContainer,
  DefaultTheme as NavigationDefaultTheme,
  DarkTheme as NavigationDarkTheme
} from '@react-navigation/native';
import {
  Provider as PaperProvider,
  DefaultTheme as PaperDefaultTheme,
  DarkTheme as PaperDarkTheme
} from 'react-native-paper';
import { createDrawerNavigator } from '@react-navigation/drawer';
import AsyncStorage from '@react-native-community/async-storage';
import Fire from './src/api/Fire';
import firebase from 'firebase';

import AppTabs from "./src/stacks/AppTabs";
import AuthStack from "./src/stacks/AuthStack";
import { DrawerContent } from "./src/screens/DrawerContent";
import Spinner from "./src/components/Spinner";
import { AuthContext } from './src/components/Context';


const Drawer = createDrawerNavigator();


export default function App() {
  const [isDarkTheme, setIsDarkTheme] = useState(false);

  {/* Themes */ }
  const CustomDefaultTheme = {
    ...NavigationDefaultTheme,
    ...PaperDefaultTheme,
    colors: {
      ...NavigationDefaultTheme.colors,
      ...PaperDefaultTheme.colors,
      background: '#ffffff',
      text: '#333333'
    }
  }

  const CustomDarkTheme = {
    ...NavigationDarkTheme,
    ...PaperDarkTheme,
    colors: {
      ...NavigationDarkTheme.colors,
      ...PaperDarkTheme.colors,
      background: '#333333',
      text: '#ffffff'
    }
  }

  const theme = isDarkTheme ? CustomDarkTheme : CustomDefaultTheme;

  const [state, dispatch] = useReducer(
    (prevState, action) => {
      switch (action.type) {
        case 'RESTORE_TOKEN':
          return {
            ...prevState,
            userToken: action.token,
            isLoading: false,
          };
        case 'SIGN_IN':
          return {
            ...prevState,
            email: action.id,
            isSignout: false,
            userToken: action.token,
            isLoading: false,
          };
        case 'SIGN_OUT':
          return {
            ...prevState,
            email: null,
            isSignout: true,
            userToken: null,
            isLoading: false,
          };
        case 'REGISTER':
          return {
            ...prevState,
            email: action.id,
            isLoading: false,
            userToken: action.token,
          };
      }
    },
    {
      isLoading: true,
      isSignout: false,
      email: null,
      userToken: null,
    }
  );


  const authContext = useMemo(() => ({
    signIn: async data => {
      // In a production app, we need to send some data (usually username, password) to server and get a token
      // We will also need to handle errors if sign in failed
      // After getting token, we need to persist the token using `AsyncStorage`
      // In the example, we'll use a dummy token

      dispatch({ type: 'SIGN_IN', id: email, token: userToken });
    },
    signOut: async data => {
      dispatch({ type: 'SIGN_OUT' })
    },
    signUp: async data => {
      // In a production app, we need to send user data to the server and get a token
      // We will also need to handle errors if sign up failed
      // After getting token, we need to persist the token using `AsyncStorage`
      // In the example, we'll use a dummy token

      dispatch({ type: 'REGISTER', id: email, token: userToken });
    },
    toggleTheme: () => {
      setIsDarkTheme(isDarkTheme => !isDarkTheme);
    }
  }),
    []
  );


  useEffect(() => {
    setTimeout(async () => {
      let userToken;
      userToken = null;
      try {
        userToken = await AsyncStorage.getItem('userToken');
      } catch (e) {
        console.log(e);
      }
      dispatch({ type: 'RESTORE_TOKEN', token: userToken });
    }, 1000);
  }, []);

  if (state.isLoading) {
    return (
      <Spinner />
    );
  }

  return (
    <PaperProvider theme={theme}>
      <AuthContext.Provider value={authContext}>
        <NavigationContainer theme={theme}>
          {state.userToken !== null ? (
            <Drawer.Navigator drawerContent={props => <DrawerContent {...props} />} >
              <Drawer.Screen name="HomeDrawer" component={AppTabs} />
              <Drawer.Screen name="ProfileDrawer" component={Profile} />
              <Drawer.Screen name="SettingsDrawer" component={Settings} />
              <Drawer.Screen name="PhoneDrawer" component={Phone} />
            </Drawer.Navigator>
          )
            :
            <AuthStack />
          }
        </NavigationContainer>
      </AuthContext.Provider>
    </PaperProvider>
  )
}

The firebase API keys have been initialized in this imported file.

import Fire from './src/api/Fire';

I have seen some code that wraps FirebaseAuth around the NavigationContainer, wouldn't AuthContext.Provider do the same thing?

I'm calling the SingIn and SignUp on their respective screens using useContext, like this

const { signUp } = useContext(AuthContext); // signup screen
const { signIn } = useContext(AuthContext); // signin screen

then using them in the onPress function like onPress={()=>{signUp(email, username, paswword)}}

Upvotes: 3

Views: 544

Answers (1)

B_M
B_M

Reputation: 173

I got an idea of what to do from this link is found: use-auth

For the useReducer hook

const [state, dispatch] = useReducer(
    (prevState, action) => {
      switch (action.type) {
        case 'RESTORE_TOKEN':
          return {
            ...prevState,
            user: action.payload.user,
            userToken: action.token,
            isLoading: false,
          };
        case 'SIGN_IN':
          return {
            ...prevState,
            user: action.payload.user,
            isSignout: false,
            userToken: action.token,
            isLoading: false,
          };
        case 'SIGN_OUT':
          return {
            ...prevState,
            user: null,
            isSignout: true,
            userToken: null,
            isLoading: false,
          };
        case 'REGISTER':
          return {
            ...prevState,
            isLoading: false,
          };
        default:
          throw new Error(`No case for type ${action.type} found.`)
      }
    },
    {
      isLoading: true,
      isSignout: false,
      userToken: null,
      user: null,
    }
  );

authContext

function getRef() {
    return firebase.database().ref();
  }

const authContext = useMemo(() => ({
    signIn: async (email, password, user) => {
      await firebase.auth().signInWithEmailAndPassword(email.trim(), password)
        .then(() => {
          state.isLoading = false
        })
        .catch(function (error) {
          // Handle Errors here.
          const errorCode = error.code
          const errorMessage = error.message
          alert(errorMessage)
          state.isLoading = false
        })
      dispatch({
        type: 'SIGN_IN',
        payload: {
          user: firebase.auth().currentUser,
        },
      });
    },

    signOut: async () => {
      firebase.auth().signOut()
        .then(function () {
          // Sign-out successful.
          state.isLoading = false
        })
        .catch(function (error) {
          // An error happened.
          state.isLoading = false
        })
      dispatch({ type: 'SIGN_OUT' })
    },

    signUp: async (email, password, avatar, displayName, phoneNumber, about) => {
      try {
        await firebase.auth().createUserWithEmailAndPassword(email.trim(), password)
          .then((userInfo) => {
            userInfo.user.updateProfile({
              displayName: displayName,
              photoURL: avatar,
              phoneNumber: phoneNumber,
            })
            console.log(userInfo);
          })
          .then(() => {
            firebase.auth().onAuthStateChanged(user => {
              getRef().child('users')
                .push({
                  avatar: avatar,
                  email: email,
                  name: displayName,
                  phoneNumber: phoneNumber,
                  aboutMe: about,
                  uid: user.uid
                })
            })
          })
      }
      catch (error) {
        alert(error);
      }

      dispatch({
        type: 'REGISTER'
      })
    },

    toggleTheme: () => {
      setIsDarkTheme(isDarkTheme => !isDarkTheme);
    }
  }),
    []
  );

useEffect hook

useEffect(() => {
    setTimeout(async () => {
      firebase.auth().onAuthStateChanged(function (user) {
        if (user) {
          // User is signed in.
          if (onAuthStateChange.current) {
            onAuthStateChange.current = false
            return
          }
          dispatch({
            type: 'RESTORE_TOKEN',
            payload: {
              user,
            },
          })
        } else {
          // User is signed out.
          dispatch({
            type: 'SIGN_OUT',
            payload: {
              user: null,
            },
          })
        }
      })
    }, 1000);
  }, []); 

And the return function

return (
    <PaperProvider theme={theme}>
      <AuthContext.Provider value={authContext}>
        <NavigationContainer
          theme={theme}
        >
          {state.user !== null ? (
            <Drawer.Navigator drawerContent={props => <DrawerContent {...props} />} >
              <Drawer.Screen name="HomeDrawer" component={AppTabs} />
              <Drawer.Screen name="ProfileDrawer" component={Profile} />
              <Drawer.Screen name="SettingsDrawer" component={Settings} />
              <Drawer.Screen name="PhoneDrawer" component={Phone} />
            </Drawer.Navigator>
          )
            :
            <AuthStack />
          }
        </NavigationContainer>
      </AuthContext.Provider>
    </PaperProvider>
  )

If you notice something used incorrectly, please advise. Otherwise, this is what's working for me, for now. I'm still trying to keep the state of the theme when it is changed, even after the app is restarted.

Upvotes: 2

Related Questions