React-native app: navigation.navigate is not a function - firebase

(In 'navigation.navigate("Home")', 'navigation.navigate' is undefined)
const handleLogin = () => {
auth
.signInWithEmailAndPassword(email, password)
.then((userCredentials) => {
const user = userCredentials.user;
console.log("Logged in with:", user.email);
navigation.navigate("Home");
})
.catch((error) => alert(error.message));
};
How can I fix this? I want to make it so that when I press the Login button, user is identified and then I go from login screen to home screen.

First you need to create navigation object by useNavigation hook like this.
const navigation = useNavigation()
For using useNavigation you need to install #react-navigation/native
npm i #react-navigation/native
Then import useNavigation from #react-navigation/native
import {useNavigation} from '#react-navigation/native'
Then you can navigate easily
const navigation = useNavigation()
navigation.navigate("Home")

Related

Reloading Expo app logs out Firebase User

I have an Expo app that I am building that uses Firebase. I have the following code that handles when a user's auth state changes. When it changes, it sets a context property and shows a specific stack dependent on the status of the user.
export default function RootNavigator() {
const { user, setUser } = useContext(AuthenticatedUserContext);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const unsubscribeAuthStateChanged = getAuth().onAuthStateChanged((authenticatedUser) => {
if (authenticatedUser) setUser(authenticatedUser);
else setUser(null);
setIsLoading(false);
console.log(authenticatedUser);
});
return unsubscribeAuthStateChanged;
}, [user]);
if (isLoading) {
return <LoadingScreen />;
}
return <NavigationContainer>{user ? <AppStack /> : <AuthStack />}</NavigationContainer>;
}
However, when I reload the expo app by pressing "R" in the terminal, my firebase user is null again.
I am using the following code to log a user in:
signInWithEmailAndPassword(auth, emailAddress, password).catch((error) => handleSignInError(error));
Any help would be greatly appreciated. Thank you!

Best way to capture screen time and press events using React Native Expo and Firebase Analytics

My group and I are currently working on a mobile app using expo-cli and firebase as the backend. One of the requirements is we need to get users' screen time and record how frequently users press certain buttons. According to expo firebase documentation, it only supports limited Firebase Analysis. We were wondering what would be the best way to use Firebase Analytics with Expo to capture screen time and button pressed frequencies.
Screen Tracking
Screen tracking in React Native is different than in a native app since some navigation libraries run inside one Activity/Viewcontroller.
Assuming you are using react-native-navigation, which does have full native navigation support you can handle screen tracking like this.
import analytics from '#react-native-firebase/analytics';
import { Navigation } from 'react-native-navigation';
Navigation.events().registerComponentDidAppearListener(async ({ componentName, componentType }) => {
if (componentType === 'Component') {
await analytics().logScreenView({
screen_name: componentName,
screen_class: componentName,
});
}
});
Look here for the documentation
If you are using react-navigation you can still work around the lack of native screen support by hooking into the events that are provided.
import analytics from '#react-native-firebase/analytics';
import { NavigationContainer } from '#react-navigation/native';
const App = () => {
const routeNameRef = React.useRef();
const navigationRef = React.useRef();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
routeNameRef.current = navigationRef.current.getCurrentRoute().name;
}}
onStateChange={async () => {
const previousRouteName = routeNameRef.current;
const currentRouteName = navigationRef.current.getCurrentRoute().name;
if (previousRouteName !== currentRouteName) {
await analytics().logScreenView({
screen_name: currentRouteName,
screen_class: currentRouteName,
});
}
routeNameRef.current = currentRouteName;
}}
>
...
</NavigationContainer>
);
};
export default App;
Here you can find a full example starter app.
Button Press Events
For logging press events there's a lot of documentation on the RNFirebase website.
A simple example to track a custom event that could be an onPress or anything would look like this:
import react, { useEffect } from 'react';
import { View, Button } from 'react-native';
import analytics from '#react-native-firebase/analytics';
function App() {
return (
<View>
<Button
title="Add To Basket"
onPress={async () =>
await analytics().logEvent('onPressAddToBasket', {
id: 3745092,
item: 'Your product name',
description: ['round neck', 'long sleeved'],
size: 'L',
wheneverYouWantToTrack: true,
})
}
/>
</View>
);
}

How can I log in a user right after his/her email has been verified using firebase/auth and react-native without creating a whole landing page?

Notice: I have seen this question, but creating a whole landing page just to verify a user seems a bit much.
I added a login functionality to my react-native app using firebase/auth with email and password. This works well so far and I have no issues doing that.
I then continued to send a verification email to a new user and only allow him/her to use the app, once the email is verified. Again, no issues here.
The next step would be to login the user right after the email was verified. This is where I'm stuck, since the onAuthStateChanged eventhandler doesn't update after the user pressed the verification link in the email.
Is there any way to listen to the emailVerified state in real-time? I tried to use polling with setInterval() but this is not great since there is a notable delay between verification and login. I read about a continueLink you can pass to sendEmailVerification, but I couldn't figure out how to make that work in react-native.
I'm using Expo and therefore the Firebase SDK, not the Firebase react native package.
Here is the code I use for the signup:
export const signUp = async (username: string, email: string, password: string) => {
try {
const auth = getAuth();
if (email && password && username) {
// sign up
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
// save username in firestore
await setUserName(userCredential, username);
// send Email Verification
await sendEmailVerification(userCredential.user);
return true;
}
} catch (error) {
onError(error);
}
};
And this is my onAuthStateChanged handler:
auth.onAuthStateChanged(authenticatedUser => {
try {
if (authenticatedUser?.emailVerified) {
setUser(authenticatedUser)
} else {
setUser(null)
}
} catch (error) {
console.log(error);
}
});
So in the end I did follow this question, but I changed it a bit to fit my needs. I'll post my steps for anyone who's doing the same.
Create a simple static website with firebase init and host it on firebase or somewhere else (check the hosting tab in your firebase console to get started)
Follow this guide to create the appropriate handlers on the website
Add the following to your verificationHandler to update the user (don't forget to import firestore) (I send the userId via the continueURL, but there are probably better ways)
// You can also use realtime database if you want
firebase.firestore().collection("users").doc(userId).set({
emailVerified: true
}, {merge: true}).then(() => {
message.textContent = "Your email has been verified.";
}).catch((error) => {
message.textContent = "The verification was invalid or is expired. Please try to send another verification email from within the app.";
});
Got to authentication -> templates in your firebase console and change the action url to your hosted website's url
Add a listener to the firestore doc to your react-native app
const onUserDataChanged = (uid, callback) => {
onSnapshot(doc(firestore, "users", uid), doc => callback(doc.data()));
}
Use the data from the callback to update the login state in the app
// As an example
auth.onAuthStateChanged(authenticatedUser => {
if (authenticatedUser && !authenticatedUser.emailVerified) {
unsubscribeFirestoreListener?.();
unsubscribeFirestoreListener = onUserDataChanged(authenticatedUser.uid, (data: any) => {
if (data?.emailVerified) {
setUser(authenticatedUser);
unsubscribeFirestoreListener?.();
}
});
}
}
use the codes below for your authentication context. for user id, you should use 'user.uid'
import React, { useState, createContext } from "react";
import * as firebase from "firebase";
import { loginRequest } from "./authentication.service";
export const AuthenticationContext = createContext();
export const AuthenticationContextProvider = ({ children }) => {
const [isLoading, setIsLoading] = useState(false);
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
firebase.auth().onAuthStateChanged((usr) => {
if (usr) {
setUser(usr);
setIsLoading(false);
} else {
setIsLoading(false);
}
});
const onLogin = (email, password) => {
setIsLoading(true);
firebase.auth().signInWithEmailAndPassword(email, password)
.then((u) => {
setUser(u);
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
setError(e.toString());
});
};
const onRegister = (email, password, repeatedPassword) => {
setIsLoading(true);
if (password !== repeatedPassword) {
setError("Error: Passwords do not match");
return;
}
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((u) => {
setUser(u);
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
setError(e.toString());
});
};
const onLogout = () => {
setUser(null);
firebase.auth().signOut();
};
return (
<AuthenticationContext.Provider
value={{
isAuthenticated: !!user,
user,
isLoading,
error,
onLogin,
onRegister,
onLogout,
}}
>
{children}
</AuthenticationContext.Provider>
);
};

How to properly persist login state using FireBase in React Native EXPO?

I used email and password to sign in through Firebase. However, once I reload the app, I need to sign in again. Is there a way to automatically log user in once the app is reloaded?
I am using EXPO managed project with functional structure btw, not with class structure.
if you're still facing this problem.
Here's how I've done it.
Here's my App.js
const [loggedIn, setLoggedIn] = useState(false);
useEffect(() => {
return firebase.auth().onAuthStateChanged(setLoggedIn);
}, []);
if (loggedIn) {
return <HomeScreen />
} else {
<Login />
}
Here's the login code.
I have a simple form with email and password and a button, when pressed, this functions is called.
const handleLogin = async () => {
await firebase
.auth()
.signInWithEmailAndPassword(email, password)
.catch((err) => {
setVisible(true);
setModalMessage(err.message);
});
};
Let me know if this worked.

How to add custom error message notification in login page

I have started integrating the react-admin page, In the login page, I'm trying to manage the error message in the auth provider. I'm not able to get the error message to display in the notification .any one please help.
login: ({ username, password }) => {
const request = { 'email': username, 'password': password}
return axios.post(process.env.REACT_APP_URL+`/login`, request).then(response => {
let result = response.data;
if(result.success){
} else {
console.log(response.data);
throw new HttpError(result.message);
}
// localStorage.setItem('username', username);
return Promise.resolve();
}).catch(error => {
return error;
});```
You can use the useNotify function from react-admin to render custom notifications.
//Import the notification
`import {useNotify} from 'react-admin';`
...
...
//Place this line in your component.
`const notify = useNotify();`
...
...
//Place this wherever you want to show notification
`notify('Message of Notification');`
//For error notifications
`notify('Error Notification','error);`
More on https://marmelab.com/react-admin/Actions.html
Edit: on reading the comment realised that this requires custom error messages.
https://marmelab.com/react-admin/doc/2.8/Translation.html
You would need to replace the messages from 'ra-language-english' with your own.
import React from 'react';
import { Admin, Resource } from 'react-admin';
import englishMessages from 'ra-language-english';
const englishCustomMessages = englishMessages;
englishCustomMessages.ra.auth.sign_in_error = 'Your Custom Message goes here';
const messages = {
en: englishCustomMessages,
}
const i18nProvider = locale => messages[locale];
const App = () => (
<Admin locale="en" i18nProvider={i18nProvider}>
...
</Admin>
);
export default App;
If you want to customize more notifications look at this file:
https://raw.githubusercontent.com/marmelab/react-admin/89ac783fd9f961401d1c2e8d4ca4965053ed1d21/packages/ra-language-english/index.js
Don't import directly as you may not stay updated with the changes in react-admin. Always override only the required changes.

Resources