Working With ReactJS and OvermindJS - Integrating Firebase for Data Storage

Aaron K Saunders - May 8 '20 - - Dev Community

We have been working through a simple authentication and create account flow using OvermindJS and ReactJS, using Ionic Framework React Components for the User Interface. The application covers Create Account, Save User Information, Login & Logout.

We are currently tracking the user information and the authentication status in the OvermindJS State, without managing the interaction with a real data backend which is what we are covering here. We will add a firebase api to the application and the integration point will be through effects.ts

Initialization

First for initialization, we can revisit our onInitialize function in overmind/index.ts. The call remains the same, but we will integrate firebase initialization to set the state and get the current user if necessary.

// overmind/index.ts
export const onInitialize: OnInitialize = async (
  { state, effects },
  overmind
) => {
  let user = await effects.initialize();
  state.currentUser = user;
  state.initialized = true;
};
Enter fullscreen mode Exit fullscreen mode

TheonInitialize is used for setting the information based on the result from the effects call to firebase API.

// effects.ts
export const initialize = () => {
  return firebaseAPI.authInit();
};

// firebase-data.ts
export const authInit = () => {
  return new Promise((resolve, reject) => {
    const unsubscribe = firebaseApp
      .auth()
      .onAuthStateChanged((firebaseUser) => {
        unsubscribe();
        resolve(firebaseUser);
        return;
      });
  });
};
Enter fullscreen mode Exit fullscreen mode

Create User

type UserInfo = {
  email: string;
  firstName: string;
  lastName: string;
  password: string;
  uid?:string
}
Enter fullscreen mode Exit fullscreen mode

Overmind also provides a functional API to help you manage complex logic. This API is inspired by asynchronous flow libraries like RxJS, though it is designed to manage application state and effects.

Using the more functional approach to creating a user, we get introduced to operators.

Using various operators, piped together and checking for errors we can create the user, and additional user data while properly managing errors.

We are using pipe and catchError in this example today.

We want to executed the firebase api call to create the user and then store additional data in a new record in the user collection.

// actions.ts
export const createAccountAndUserRecord: Operator<void, any> = pipe(
  mutate(async ({ state, effects }, userInfo: any) => {
    state.error = null;
    state.currentUser = null;
    let { email, password } = userInfo;
    let newUserInfo = await effects.createAccount({ email, password });
    state.currentUser = { ...newUserInfo.user, uid: newUserInfo?.user?.uid };
  }),
  mutate(
    async ({ state, effects }, userInfo: any) => {
      let newUserInfo = await effects.createUserRecord(userInfo);
      state.currentUser = { ...newUserInfo?.data() };
    }
  ),
  catchError(({ state }, error: Error): Operator<Error, never> => {
    state.error = error;
    throw new Error(error.message);
  })
);
Enter fullscreen mode Exit fullscreen mode

Looking into the associated effects.ts file where we call the APIs in the firebase-data.ts file

// effects.ts - create user
export const createAccount = async (userInfo: {
  email: string;
  password: string;
}) => {
  return await firebaseAPI.createAccount(userInfo);
};

// firebase-data.ts - firebase create user
export const createAccount = ({ email, password }:{
  email: string;
  password: string;
}) => {
  return firebaseApp.auth()
          .createUserWithEmailAndPassword(email, password);
};
Enter fullscreen mode Exit fullscreen mode

Now we need to create the entry in the user collection by calling effects.createUserRecord

// effects.ts - create user record
export const createUserRecord = async (userInfo: {
  email: string;
  firstName: string;
  lastName: string;
  uid: string;
}) => {
  return await firebaseAPI.createUserRecord(userInfo);
};

// firebase-data.ts  - firebase create user record
export const createUserRecord = async (info: {
  email: string;
  firstName: string;
  lastName: string;
  uid: string;
}) => {
  let usersRef = firebase.firestore().collection("users").doc(info.uid);
  let created = firebase.firestore.Timestamp.fromDate(new Date());

  let newUserData = {
    ...info,
    created,
  };

  await usersRef.set(newUserData);

  return (await usersRef.get()).data();

};
Enter fullscreen mode Exit fullscreen mode

In the file firebase-data.ts where we create user record by adding it to the users collection; you can see in the end where we are querying the record again.

return (await usersRef.get()).data();
Enter fullscreen mode Exit fullscreen mode

That is because we want the user record with all of the data including the timestamp which was generated by the firebase server.

Login User

This is pretty straight forward, no a use of operators, just a straight call from actions to effects to firebase api

export const doLogin: Action<any, any> = async (
  { state, effects },
  credentials: { email: string; password: string }
) => {
  try {
    state.error = null;
    state.currentUser = null;

    let { user } = await effects.login(credentials);

    state.currentUser = {
      email: user?.email,
      username: user?.displayName || user?.email,
      uid: user?.uid,
    };
    return state.currentUser;
  } catch (error) {
    state.currentUser = null;
    state.error = error;
    return error;
  }
};
Enter fullscreen mode Exit fullscreen mode

The actions and effects are literally just passing through the credential parameters from one to another.

// effects.ts - login
export const login = async ({
  email,
  password,
}: {
  email: string;
  password: string;
}) => {
  return await firebaseAPI.login(email, password);
};

// firebase-data.ts
export const login = (email: string, password: string) => {
  return firebaseApp.auth().signInWithEmailAndPassword(email, password);
};
Enter fullscreen mode Exit fullscreen mode

Logout User

This is pretty straight forward also, no a use of operators, just a straight call from actions to effects to firebase api

// actions.ts
export const doLogout: AsyncAction<void, boolean> = async ({
  state,
  effects,
}) => {
  state.error = null;
  state.currentUser = null;

  await effects.logout();
  return true;
};
Enter fullscreen mode Exit fullscreen mode

Once again, the actions and effects are literally just passing through the credential parameters from one to another.

// effects.ts
export const logout = async () => {
  return await firebaseAPI.logout();
};

// firebase-data.ts
export const logout = async () => {
  return await firebaseAPI.logout();
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

This is the last piece of the puzzle to create the account and to work through authentication with firebase as the backend database. There certainly are additional optimizations and changes that could be made, but this was meant to be a simple introduction to the concepts.

Please checkout the associated videos on YouTube and the source code in the GitHub Repo.

GitHub logo aaronksaunders / user-login-overmind-react

User Authentication & Account Creation Pattern In ReactJS with Ionic Framework & OvermindJS for state management

user-login-overmind-react

YOUTUBE TUTORIALS COVERING IONIC & REACTJS

#reactjs #javascript #overmindjs

User Authentication Pattern In ReactJS Using OvermindJS

Simple authentication flow using overmindjs and reactjs, using ionic framework components for the UI.

Tracking User Authentication State In ReactJS Using OvermindJS

Setup

The firebase information is stored in an env.js file that needs to be added to your project with your specific credentials

export const FIREBASE_CONFIG =  { 
  [ YOUR CREDENTIALS HERE ]
}

See Tags For Functionality

Associated Links






. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .