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;
};
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;
});
});
};
Create User
type UserInfo = {
email: string;
firstName: string;
lastName: string;
password: string;
uid?:string
}
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);
})
);
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);
};
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();
};
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();
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;
}
};
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);
};
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;
};
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();
};
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.
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
- Login/Logout - TAG-LOGIN-LOGOUT
- Create Account - TAG-CREATE-ACCOUNT
- Added Firebase - TAG-ADDED-FIREBASE
Associated Links
- Video https://www.youtube.com/watch?v=7u7AQ3YYteg
- Other Example - https://codesandbox.io/s/ionic-react-overmind-simple-0l8ue?file=/src/overmind/index.js
- Overmindjs - https://overmindjs.org/
- previous video - https://www.youtube.com/watch?v=IFbQ6jlQYWs
- source code - https://github.com/aaronksaunders/user-login-overmind-react
Top comments (0)