How to solve React Native expo authSession google login issue? - react-native

I wanted to implement login with google feature on my React native app. I'm using expo-cli and I used expo authSession for this.
LoginScreen.js
import * as React from "react";
import * as WebBrowser from "expo-web-browser";
import * as Google from "expo-auth-session/providers/google";
import { Button, View, Text, TouchableOpacity } from "react-native";
WebBrowser.maybeCompleteAuthSession();
export default function LoginScreen() {
const [googleAccessToken, setGoogleAccessToken] = React.useState(null);
const [userInfo, setUserInfo] = React.useState(false);
const [request, response, promptAsync] = Google.useAuthRequest({
expoClientId: "###",
iosClientId: "###",
androidClientId: "###",
webClientId: "###",
});
React.useEffect(() => {
if (response?.type === "success") {
const { authentication } = response;
setGoogleAccessToken(authentication.accessToken);
fetchUserInfo();
}
}, [response]);
const fetchUserInfo = () => {
if (googleAccessToken) {
fetch("https://www.googleapis.com/oauth2/v3/userinfo", {
headers: { Authorization: `Bearer ${googleAccessToken}` },
})
.then((response) => response.json())
.then((userInfoObj) => {
setUserInfo(userInfoObj);
console.log(userInfoObj);
})
.catch((err) => {
console.log(err);
});
}
};
return (
<View>
<Button
disabled={!request}
title="Login Here"
onPress={() => {
promptAsync();
}}
/>
{userInfo ? <Text>{userInfo.email}</Text> : <Text>Nope</Text>}
</View>
);
}
But once, I click LOGIN HERE button and login with google account it doesn't set userInfo state in the first time. I have to click LOGIN HERE button again to login. Even though I have authenticated with google, it doesn't set userInfo state in the first time. But once I click LOGIN HERE again and after continue the process, then it works. How can I solve this issue?
Also I'm getting this warning as well.
Warning
EventEmitter.removeListener('url', ...): Method has been deprecated. Please instead use `remove()` on the subscription returned by `EventEmitter.addListener`.
at node_modules/react-native/Libraries/vendor/emitter/_EventEmitter.js:164:4 in EventEmitter#removeListener
at node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter.js:108:4 in removeListener
at node_modules/react-native/Libraries/Linking/Linking.js:57:4 in removeEventListener
at node_modules/expo-web-browser/build/WebBrowser.js:354:4 in _stopWaitingForRedirect
at node_modules/expo-web-browser/build/WebBrowser.js:347:31 in _openAuthSessionPolyfillAsync

I found the answer for this question by myself. You need to put fetchUserInfo() inside useEffect.
import * as React from "react";
import * as WebBrowser from "expo-web-browser";
import * as Google from "expo-auth-session/providers/google";
import { Button, View, Text, TouchableOpacity } from "react-native";
WebBrowser.maybeCompleteAuthSession();
export default function LoginScreen() {
const [googleAccessToken, setGoogleAccessToken] = React.useState(null);
const [userInfo, setUserInfo] = React.useState(false);
const [request, response, promptAsync] = Google.useAuthRequest({
expoClientId: "###",
iosClientId: "###",
androidClientId: "###",
webClientId: "###",
});
React.useEffect(() => {
const fetchUserInfo = () => {
if (googleAccessToken) {
fetch("https://www.googleapis.com/oauth2/v3/userinfo", {
headers: { Authorization: `Bearer ${googleAccessToken}` },
})
.then(response => response.json())
.then(userInfoObj => {
setUserInfo(userInfoObj);
console.log(userInfoObj);
})
.catch(err => {
console.log(err);
});
}
};
if (response?.type === "success") {
const { authentication } = response;
setGoogleAccessToken(authentication.accessToken);
fetchUserInfo();
}
}, [response]);
return (
<View>
<Button
disabled={!request}
title="Login Here"
onPress={() => {
promptAsync();
}}
/>
{userInfo ? <Text>{userInfo.email}</Text> : <Text>Nope</Text>}
</View>
);
}

Related

Why isn't the google accounts login page showing up even though I have implemented the expo auth session library with react native?

I have been following the tinder 2.0 react native tutorial https://youtu.be/qJaFIGjyRms At 1:04:00 he sets the sign in method to: "await Google.logInAsync()" but I have noticed the google app auth library used in the video is now deprecated, I am redirected to use expo auth session instead, with this new library I cannot tell whether the google sign in is working or not as I am simply redirected back to the homepage after clicking the login button.
Here is my code with response printed in the console:
Screenshot:
code:
import React, { createContext, useContext } from 'react'
import * as WebBrowser from "expo-web-browser";
import { Button } from "react-native";
import * as Google from "expo-auth-session/providers/google";
import { useEffect, useState } from "react";
import { useNavigation } from "#react-navigation/native";
import { GoogleAuthProvider, signInWithCredential } from 'firebase/auth';
const AuthContext = createContext({});
const user = null
WebBrowser.maybeCompleteAuthSession();
const GoogleLogin = () => {
const navigation = useNavigation();
const [request, response, promptAsync] = Google.useAuthRequest({
expoClientId:
"236293699216-bst43767un873mcddmmrpgf4v2h088jd.apps.googleusercontent.com",
iosClientId:
"236293699216-6jdpm0rd6kn5d0qlbh1vgva5afgbqgib.apps.googleusercontent.com",
webClientId:
"236293699216-9a0nknjdq7ie79h40iubg0tddokgogfv.apps.googleusercontent.com",
scopes: ["profile", "email"],
permissions: ["public_profile","email", "gender", "location"],
});
const asyncAuthRequest = async () => {
if (response?.type === "success") {
const { authentication } = response;
// await AsyncStorage.setItem("accessTocken", "hihi");
//navigation.navigate"Home");
const { idToken, accessToken} = response;
const credential = GoogleAuthProvider.credential(idToken, accessToken);
await signInWithCredential(auth, credential)
}
return Promise.reject();
};
useEffect(() => {
asyncAuthRequest();
}, [response]);
console.log('response', response)
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
};
export default GoogleLogin;

Problem with Expo Google signin on Device

I followed every single step on the tutorial page of Expo.dev to create a login page using Google Oauth.
I get the browser to open and I select a google account but the response I get back is *
Object {
"type": "dismiss",
}
Here is my code, literally copied and pasted from the tutorial page of the official expo dev site
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import * as Google from 'expo-auth-session/providers/google';
import { Button } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
export default function App() {
const [request, response, promptAsync] = Google.useAuthRequest({
expoClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
iosClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
androidClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
webClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
});
React.useEffect(() => {
if (response?.type === 'success') {
const { authentication } = response;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}

React Native Navigate To Another Screen Automatically

I am trying to navigate back to the LogIn screen as soon as logout.js is hit.
At one point this seemed to work and then stopped and I can't work out why.
Here is my code:
import React, { Component } from 'react';
import { Text, View, Button } from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { globalStyles } from '../styles/global';
export default class App extends Component {
componentDidMount() {
(async () => {
const token = await SecureStore.getItemAsync('token');
// Your fetch code
this.setState({loaded:false, error: null});
let url = 'https://www.example.com/api/auth/logout';
let h = new Headers();
h.append('Authorization', `Bearer ${token}`);
h.append('Content-Type', 'application/json');
h.append('X-Requested-With', 'XMLHttpRequest');
let req = new Request(url, {
headers: h,
method: 'GET'
});
fetch(req)
.then(response=>response.json())
//.then(this.showData)
.catch(this.badStuff)
})();
this.deleteToken()
}
badStuff = (err) => {
this.setState({loaded: true, error: err.message});
}
deleteToken() {
(async () => {
const token = await SecureStore.deleteItemAsync('token')
});
this.goToLogInScreen()
}
goToLogInScreen() {
this.props.navigation.navigate('LogIn')
}
render() {
return (
<View style={globalStyles.container}>
<Button
title="Go to Details"
onPress={() => this.props.navigation.navigate('LogIn')}
/>
</View>
);
}
}
I need the code to:
Send the command to the API to log out
Delete the token from SecureStore
Navigate to the LogIn screen (containing the log in form).

Infinite FlatList problem - React Native , Expo

I am using Expo for developing react-native applications.
I want to make an Infinite list, but every time onEndReached event is fired, FlatList is refreshed automatically scrolls to the top of the page!
import React, { useState, useEffect } from "react";
import { SafeAreaView, Text, FlatList } from "react-native";
export default function App() {
const [config, setConfig] = useState({
result: [],
page: 0
});
async function fetchData() {
const response = await fetch("http://192.168.2.49:3500/q", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({ page: config.page })
});
const data = await response.json();
setConfig({
result: [...config.result, ...data],
page: config.page++
});
}
const onEndReached = async () => {
await setConfig({
page: config.page++
});
fetchData();
};
useEffect(() => {
fetchData();
}, []);
return (
<SafeAreaView>
<Text>Current Page : {config.page}</Text>
<FlatList
data={config.result}
renderItem={o => <Text>X :{o.item.t.c}</Text>}
keyExtractor={item => item._id}
onEndReached={() => onEndReached()}
onEndReachedThreshold={0}
></FlatList>
</SafeAreaView>
);
}
You are calling setConfig twice, before calling fetchData and after a successful API request. Which triggers a rerender.
I refactored your code, try this.
import React, { useState, useEffect, useCallback } from 'react';
import {
SafeAreaView,
Text,
FlatList,
NativeSyntheticEvent,
NativeScrollEvent,
} from 'react-native';
export default function App() {
const [config, setConfig] = useState({
result: [],
page: 0,
});
const [isScrolled, setIsScrolled] = useState(false);
const fetchData = useCallback(() => {
async function runFetch() {
const response = await fetch('http://192.168.2.49:3500/q', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({ page: config.page }),
});
const data = await response.json();
setConfig({
result: [...config.result, ...data],
page: config.page++,
});
}
runFetch();
}, [config.page, config.result]);
const onEndReached = useCallback(() => fetchData(), [fetchData]);
const onScroll = useCallback(
e => setIsScrolled(e.nativeEvent.contentOffset.y > 0),
[],
);
useEffect(() => {
fetchData();
}, [fetchData]);
return (
<SafeAreaView>
<Text>Current Page : {config.page}</Text>
<FlatList
data={config.result}
keyExtractor={item => item._id}
onEndReached={onEndReached}
onEndReachedThreshold={0.1}
onScroll={onScroll}
renderItem={o => <Text>X :{o.item.t.c}</Text>}
/>
</SafeAreaView>
);
}

Handling Errors from Redux API Call as a Toast

So I'm trying to figure out the best way to display a Toast error and success function when the API call fires from redux.
My line of thinking: Create action for the API call. If successful, then I want the screen to change to the home screen. If it fails, then display the message in a Toast.
Here's what some of my actions look like:
export function getTokenAPI(username, password) {
return async function action(dispatch) {
try {
dispatch({ type: t.AUTH_GET_TOKEN });
dispatch(setLoading(true));
const { data } = await API.authGetToken(username, password);
const { success } = data;
if (success) {
const { access_token, refresh_token } = data;
dispatch(setAccessToken(access_token));
dispatch(setRefreshToken(refresh_token));
await dispatch(setLoading(false));
} else if (!success) {
const { errorMessage } = data;
throw Error(errorMessage);
}
} catch (e) {
dispatch(setError(e.message));
dispatch(setLoading(false));
}
};
}
The setError action sets the error key to true and sets the errorMessage. Here's what my screen looks like:
import React from 'react';
import { Container, View, Toast } from 'native-base';
import styles from './styles';
import { connect } from 'react-redux';
import { authActions } from '_ducks/auth';
const LoginScreen = props => {
const { getToken, navigation } = props;
const { navigate } = navigation;
const navigateToHome = () => navigate('Home');
const handleLogin = async () => {
const { error, errorMessage } = props;
await getToken('sample', 'pass123');
if (error) {
Toast.show({
text: errorMessage,
buttonText: 'kay',
});
} else {
navigateToHome();
}
};
return (
<Container>
<View style={styles.container}>
<LoginButton onPress={handleLogin} />
</View>
</Container>
);
};
const mapDispatchToProps = dispatch => ({
getToken: () => dispatch(authActions.getTokenAPI()),
});
const mapStateToProps = state => ({
isLoading: state.authReducer.isLoading,
error: state.authReducer.error,
errorMessage: state.authReducer.errorMessage,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LoginScreen);
So if there's an error, then display the toast. If it's successful, navigate to the home screen. Essentially, error will not be true quick enough to make the check within handleLogin work appropriately.
Any recommendations on the pattern or process? Should I be using a useEffect hook here?