I'm a newbie in React Native and struggling to make a Facebook login for my app.
I finished configuring all the requirements for my app, Firebase and Facebook for developers.
The thing is when I pressed login, at the first time, it kept repeating again the Login permission. Until the data receive the accessToken and id, my app might/might not navigate to the Main screen (login succeed). But just 1 second later, the screen prompted and showed the Login Manager again. And it keeps repeating that. I really don't know why.
Is it something wrong with my code?. I'm thinking it kept repeating because It must do that until it gets the data need ( promise resolved)
Here's the code:
import React, { useEffect, useState } from 'react';
import {
View,
ImageBackground,
StyleSheet,
Alert
} from 'react-native';
import {
Layout
} from '#ui-kitten/components';
import { GoogleSignin, GoogleSigninButton, statusCodes } from '#react-native-community/google-signin';
import { firebase } from '#react-native-firebase/auth';
import { LoginButton, LoginManager, AccessToken } from 'react-native-fbsdk';
const GoogleLogIn = (props) => {
const [userInfo, setUserInfo] = useState(null);
const [isLogIn, setIsLogIn] = useState(false);
// Facebook log in
const _facebookLogin = async () => {
try{
const result = await LoginManager.logInWithPermissions(['public_profile', 'email']);
console.log(result);
if(result.isCancelled){
console.log('Login is cancelled');
}else if(result.grantedPermissions){
console.log('DONE')
const data = await AccessToken.getCurrentAccessToken();
console.log(data);
const cred = firebase.auth.FacebookAuthProvider.credential(data.accessToken);
const firebaseCred = await firebase.auth().signInWithCredential(cred);
setIsLogIn(true);
setUserInfo(data.userID);
props.navigation.navigate('AppNavigator', {screen: 'Welcome'})
}
}catch(err){
console.log(err);
throw err;
}
}
return(
<View style={styles.background}>
<LoginButton
onLoginFinished={_facebookLogin}
onLogoutFinished={() => console.log('Logout!')}
/>
</View>
);
};
const styles = StyleSheet.create({
background: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})
export default GoogleLogIn;
Here's the weird behavior:
Error_repeating asking permissions for login Facebook
PLEASE HELP!
The useState function seems to cause rendering again and cause problems.
You can try use useCallback
useCallback will return a memoized version of the callback that
only changes if one of the dependencies has changed. This is useful
when passing callbacks to optimized child components that rely on
reference equality to prevent unnecessary renders (e.g.
shouldComponentUpdate).
Usage
const _facebookLogin = useCallback( async () => {
try{
const result = await LoginManager.logInWithPermissions(['public_profile', 'email']);
console.log(result);
if(result.isCancelled){
console.log('Login is cancelled');
}else if(result.grantedPermissions){
console.log('DONE')
const data = await AccessToken.getCurrentAccessToken();
console.log(data);
const cred = firebase.auth.FacebookAuthProvider.credential(data.accessToken);
const firebaseCred = await firebase.auth().signInWithCredential(cred);
setIsLogIn(true);
setUserInfo(data.userID);
props.navigation.navigate('AppNavigator', {screen: 'Welcome'})
}
}catch(err){
console.log(err);
throw err;
}
},[])
Here's the code I found it worked well. You can put this in a button for customizing
const facebookLogin = async () => {
try {
const result = await LoginManager.logInWithPermissions(['public_profile', 'email']).then(
(res) => {
if(res.isCancelled){
console.log('Something went wrong, please try again');
}else{
AccessToken.getCurrentAccessToken().then(
async (data) => {
console.log(data.accessToken.toString())
const cred = firebase.auth.FacebookAuthProvider.credential(data.accessToken);
const firebaseCred = await firebase.auth().signInWithCredential(cred);
setIsLogIn(true);
setUserInfo(data.userID);
props.navigation.navigate('AppNavigator', {screen: 'Welcome'})
}
)
}
}
)
} catch (err) {
console.log(err);
}
}
Related
I am relatively new to javascript and React and I am helping out with a project. I want to create a profile page for a signed in user with information stored in a firebase real time database. But the component is not rendering and the console shows 'Uncaught TypeError: Cannot read properties of null (reading 'username')'. I surmise it is because the data from the database is not being fetched before rendering. The data exists. The profile hook -
import React, { useEffect,useState } from 'react';
import {useAuth} from '../contexts/AuthContext'
import { getDatabase,ref, onValue} from "firebase/database";
function Profile(){
const [userData, setUserData] = useState({});
const currentUser = useAuth();
useEffect(()=>{ putData()
},[])
async function putData(){
let db = getDatabase();
let refd = ref(db,'users/'+ currentUser.currentUser.uid );
onValue(refd, (snapshot) => {
console.log(snapshot.val());
setUserData(snapshot.val());
},
(errorObject) => {
console.log('The read failed: ' + errorObject.name);
})
}
return(
<div>
<h3>Username : {userData.username}</h3>
<h3>Institute name : {userData.institute_name}</h3>
<h3>Accomodation : {userData.accomodation}</h3>
<h3>Phone no. : {userData.phone}</h3>
<h3>Email : {userData.email}</h3>
</div>
);
}
export default Profile;
Does the problem lie with the 'onValue' part or with the react part? Firebase documentation is not helping with my current understanding. Any help on how to accomplish this is appreciated.
useEffect(() => {
try {
//getting previously saved data
// console.log({ SelectedCaseDetails });
const getData = async () => {
const docRef = doc(
db,
"here comes your path to your document"
);
const docSnap = await getDoc(docRef);
console.log("data -->", docSnap.data());
if (docSnap.exists()) {
setData(docSnap.data());
setData(() => ({ ...docSnap.data() }));
}
};
getData();
} catch (error) {
console.log({ error });
}
}, []);
You just have to run your get data function in useEffect that runs when page is loading
Hope this helps 🤗
¯\(ツ)/¯
I have a react native project which shows a UserNavigator page consisting of two tabs, one to show active data and the second to show inactive data, all the data can be clicked to navigate to UserDetails page
The problem is when I tried to update the inactive data to active and then go back to the previous screen which is UserNavigator the data on UserNavigator will not be updated since useEffect is not triggered to fetch updated data
Tab Nav page named UserNavigator
export default function DataList() {
return (
<Tab.Navigator>
<Tab.Screen name="Active" component={ActiveData} />
<Tab.Screen name="Inactive" component={InactiveData} />
</Tab.Navigator>
)
}
useEffect on Active and Inactive page
React.useEffect(() => {
(async () => {
setLoading(true);
try {
await getUserData();
} catch (err) {
Alert.alert("", err?.response?.data?.message || err?.message);
}
setLoading(false);
})();
}, []);
function on UserDetails to set user status to active then go back to list
const onSetActive = async () => {
setModalVisible(!modalVisible)
setLoading(true)
try {
const body = {
_id: item._id,
status: "Active",
}
const response = await axiosInstance.patch(`/UserData/update`, body)
await getUserData()
Alert.alert(
"Success",
"User has been set to active!",
[
{
text: "OK",
onPress: () => navigation.navigate("UserNavigator")
}
]
);
}
catch (err) {
Alert.alert("", err?.response?.data?.message || err?.message);
}
setLoading(false)
}
I've been trying to use useIsFocused on Active and Inactive Tab
Top Import
import { useIsFocused } from "#react-navigation/native";
const isFocused = useIsFocused();
React.useEffect(() => {
(async () => {
setLoading(true);
try {
await getUserData();
} catch (err) {
Alert.alert("", err?.response?.data?.message || err?.message);
}
setLoading(false);
})();
}, [isFocused]);
But when I tried to navigate between tabs it will rerender the data even when there isn't any change to the data and it will take more time to navigate and wait for data to be fetched
Navigation.replace won't do since I navigate to UserNavigator page from Main Page if I do that when I press the back button from UserDetails it will go back to Main Page instead of UserNavigator
Is there any alternative solution to my problem? I've been thinking to pass props when navigating to UserNavigator after updating data to trigger useEffect but I don't know how
You can use your reducer engine to achieve it, you can change the variable on any page on the app and subscribe it in any page using useEffect like this example:
import React, {useEffect} from 'react';
import store from '../store';
const Page = () => {
const {isFocused} = store.getState().updateReducer;
useEffect(() => {
(async () => {
setLoading(true);
try {
await getUserData();
} catch (err) {
Alert.alert("", err?.response?.data?.message || err?.message);
}
setLoading(false);
})();
}, [isFocused]);
return (<View></View>
}
export default Page;
I'm trying to make a Username and Password Authentication in a web app that is made with react and firebase.
But I'm really new to firebase and I couldn't find any good documentation about the process, I have read about Firestore but I don't know how to connect it to the authentication method. Any help or hint is appreciated!
Here is the signup function of my /register route.
async function handleSubmit(e) {
e.preventDefault()
if (passwordRef.current.value !== passwordConfirmationRef.current.value){
return setError('Passwords do not match');
}
try {
setError('')
setLoading(true)
await signup(emailRef.current.value, passwordRef.current.value, usernameRef.current.value)
Home()
} catch {
setError('Failed to create an account')
}
setLoading(false)
}
And this is my 'AuthContext.JSX' code:
import React, { useContext , useEffect, useState } from 'react'
const AuthContext = React.createContext()
import {auth} from '../firebase'
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password)
}
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
auth.onAuthStateChanged(user => {
setCurrentUser(user)
})
const value = {
currentUser,
signup,
login
}
return (
<AuthContext.Provider value={value}>
{!loading && children }
</AuthContext.Provider>
)
}
Just to let you know the authentication is working well the only problem is getting the Display Name and exporting it to the home page for display.
I want to display the username on this page
Thanks to Dhamaraj I used the updateProfile() function inside my Register route, and it worked properly for adding a display name to the user which I used on the home page for the profile display.
That is the new function, may it helps somebody with the same issue:
async function handleSubmit(e) {
e.preventDefault()
if (passwordRef.current.value !== passwordConfirmationRef.current.value){
return setError('Passwords do not match');
}
try {
setError('')
setLoading(true)
await signup(emailRef.current.value, passwordRef.current.value)
auth.currentUser.updateProfile({
displayName: usernameRef.current.value
}).then(() => {
console.log('Username is: ' + auth.currentUser.displayName)
}).catch((error) => {
console.log('An error was occured while updating the profile.')
});
Home()
} catch {
setError('Failed to create an account')
}
setLoading(false)
}
So I'm working on a react native authentication screen. I'm storing the token in AsyncStorage (yea I know it's not the best solution).
So what's happening is when I log in, the token is stored, but the getItem on my Authentication.js screen is not being triggered, and the profile screen is not being called.
If I log in and then manually refresh the app, I am redirected to the profile screen.
Login.js
function Login({navigation}) {
const [signIn, {data}] = useMutation(USER_SIGNIN_MUTATION);
const [userName, setUserName] = useState('');
const [password, setPassword] = useState('');
function handleLogIn() {
signIn({
variables: {
email: userName,
password: password,
},
});
}
useEffect(() => {
if (data != null) {
setToken();
}
});
const setToken = async () => {
try {
console.log('before');
await AsyncStorage.setItem('token', data.signIn.token);
console.log('after');
} catch (error) {
console.log(error);
}
};
return(
...
)
}
Authentication.js
function Authentication() {
const [localToken, setLocalToken] = useState(false);
useEffect(() => {
const fetchUser = async () => {
try {
console.log('before get');
const userData = await AsyncStorage.getItem('token');
if (userData !== null) {
setLocalToken(true);
}
} catch (error) {
console.log(error);
}
};
fetchUser();
}, [localToken]);
console.log(`auth screen - ${localToken}`);
return (
<NavigationContainer>
{localToken === true ? <ProfileStack /> : <AuthStack />}
</NavigationContainer>
);
}
export default Authentication;
also same happens with the logout function when fired. (the function runs, but I need to refresh the app to get back to the login screen)
Profile.js
function Profile({navigation}) {
function signOut() {
logOut();
}
const logOut = async () => {
try {
console.log('before clear');
await AsyncStorage.removeItem('token');
console.log('after clear');
} catch (error) {
console.log(error);
}
};
return (
...
)
}
I'm grateful for any insight on this.
useEffect(() => {
const fetchUser = async () => {
try {
console.log('before get');
const userData = await AsyncStorage.getItem('token');
if (userData !== null) {
setLocalToken(true);
}
} catch (error) {
console.log(error);
}
};
fetchUser();
}, [localToken]);
Here you added the localToken variable in the dependency array of the useEffect. So you are basically saying: run this effect only if the localToken variable changes. But you change that from within the effect only. So try to remove it and keep the dependency as []. This way the effect will run when the component is rendered.
About the fact that you have to refresh the page, it is important to understand why this happens.
<NavigationContainer>
{localToken === true ? <ProfileStack /> : <AuthStack />}
</NavigationContainer>
Here you are rendering ProfileStack or AuthStack based on the localToken value. When you logout, you remove the token from the AsyncStorage but this is not enough. You actually need to trigger a rerender in the Authentication component so the localToken is reevaluated. Basically, when you logout you also need to set setLocalToken(false). So you need to access setLocalToken function from the Profile component. You can pass this function as a prop or better you can use Context API
On my Expo I am getting the following error:
I believe this is due to an Async/Await issue in my code, but I am unsure how to fix it. I am new to ReactNative, and am doing the Complete React Course by Stephen, but he does not discuss this issue at all so any help would be much appreciated.
Here is my code:
import { useEffect, useState } from 'react'
import yelp from '/Users/macbook/Coding Stuff React Native/food/src/api/yelp.js'
export default () => {
const [results, setResults] = useState([]);
const [errorMessage, setErrorMessage] = useState('');
const searchApi = async searchTerm => {
console.log('Hi there!');
try {
const response = await yelp.get('/search', {
params: {
limit: 50,
term: searchTerm,
location: 'san jose'
}
});
setResults(response.data.businesses);
} catch (err) {
setErrorMessage('Something went wrong');
}
};
// Call searchApi when component
// is first rendered. BAD CODE!
// searchApi('pasta');
useEffect(() => {
searchApi('pasta');
}, [])
return [searchApi, results, errorMessage];
};
Thank you :)
Update for anyone else stuck on this:
I needed to change
{errorMessage ? <Text>{errorMessage}</Text> : null}
to
{!!errorMessage && <Text>{errorMessage}</Text>}