How can I make the user always logged in? React Native - react-native

Using the code below, how do I get the user to be logged in every time the user opens the application?
export default class LoginScreen extends Component {
constructor() {
super();
this.state = {
email: '',
password: '',
}
}
updateInputVal = (val, prop) => {
const state = this.state;
state[prop] = val;
this.setState(state);
}
userLogin = () => {
if(this.state.email === '' && this.state.password === '') {
Alert.alert('Enter details to signin!')
} else {
firebase
.auth()
.signInWithEmailAndPassword(this.state.email, this.state.password)
.then((res) => {
console.log(res)
console.log('User logged-in successfully!')
Alert.alert('Succesfully logged')
this.setState({
email: '',
password: ''
})
this.props.navigation.navigate('Home')
})
.catch(error => this.setState({ errorMessage: Alert.alert('Not logged') }))
}
}
I'm trying to make a fashion application that includes a profile.
After that, I'm going to put the sign-out but it's already quite clear to me how to do it.
Thanks!!

firebase authentication keeps user signed in
check for login information in componentDidMount using
firebase.auth().onAuthStateChanged((user)=>{
})
if user exists navigate to home screen

Related

Check if a user is logged in or not

I have a nextjs frontend in which I have created a redux store to store user's data. In the store I have an async function getCurrentUser (gets called in useEffect in _app) which sends an api request to backend to fetch the current user's data using the cookie and the frontend sets that data in the user's state. In the store I also have a function isLoggedIn that returns true or false based on whether the user's email is null or not.
Now I have a My Profile page which should display the user's information but to display this page a user should be logged in and the user in the cookie and store should be the same. So in the useEffect of this component I send an api request to backend which takes the cookie and returns the user's email and in the frontend I check if the returned email is same as the user's (in store) email. If the emails are different then I redirect the frontend, else I display the data.
Now the problem is that if I reload the My Profile page, state's email becomes null, getCurrentUser gets called, while it is resolving and setting the user's state isLoggedIn gets called and it returns false, so the frontend redirect even though the user is logged in.
Here is my userSlice:
export const getCurrentUser = createAsyncThunk(
"counter/getCurrentUser",
async () => {
const res = await axios.get("http://localhost:8000/get-current-user", {
withCredentials: true,
});
return res.data;
}
);
const userSlice = createSlice({
name: "user",
initialState: {
username: null,
email: null,
firstName: null,
lastName: null,
gender: null,
dob: null,
},
reducers: {
changeUser: (state, action) => {
return action.payload;
},
clearUser: () => {
return {
username: null,
email: null,
firstName: null,
lastName: null,
gender: null,
dob: null,
};
},
},
extraReducers(builder) {
builder.addCase(getCurrentUser.fulfilled, (state, action) => {
return action.payload;
});
},
});
export const isLoggedIn = (state) => {
if (state.user.email === null) {
return false;
}
return true;
};
Here is my _app.js:
function MyApp({ Component, pageProps }) {
const router = useRouter();
return (
<Provider store={store}>
<Header />
<App>
<Component {...pageProps} />
</App>
</Provider>
);
}
const App = ({ children }) => {
const dispatch = useDispatch();
useEffect(() => {
// Calling the dispatch method to get current user from backend
dispatch(getCurrentUser());
});
return <>{children}</>;
};
Here is my MyProfile.js component:
const user = useSelector((state) => state.user);
const isUserLoggedIn = useSelector(isLoggedIn);
useEffect(() => {
if (!isUserLoggedIn) {
Router.push("/");
return;
}
axios
.get("http://localhost:8000/get-current-user", { withCredentials: true })
.then((res) => {
if (res.data.email !== user.email) {
Router.push("/");
return;
}
console.log(res);
})
.catch((err) => {
console.log(err);
});
});
Can anyone tell me what can I do here?

React Native using Expo changed logged in user suddenly

I'm building a small social media app using React Native, Expo, Redux and firebase.
I created multiple user accounts for testing, i logged in the app using one of them, while i was working on the app, it suddenly changed the user account I logged in with to another user!
Is it normal? If not, what would be the reason for that??
Here's how the registration is being handled:
export class Register extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
name: '',
lastname: '',
degree: '',
description:''
}
this.onSignUp = this.onSignUp.bind(this)
}
onSignUp(){
if(
this.state.email != '' &&
this.state.password != '' &&
this.state.name != '' &&
this.state.lastname != ''
){
const { email, password, name, lastname, degree, description } =
this.state;
firebase.auth().createUserWithEmailAndPassword(email, password)
.then((result) => {
firebase.firestore().collection("Doctors")
.doc(firebase.auth().currentUser.uid)
.set({
name,
email,
lastname,
degree,
description
})
console.log(result)
})
.catch((error) => {
console.log(error)
})
}
else{
alert("Make sure you filled all the fields with correct info!");
}
And Here's the App.js
export class App extends Component {
constructor(props) {
super(props)
this.state = {
loaded: false,
}
}
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
if (!user) {
this.setState({
loggedIn: false,
loaded: true
})
} else {
this.setState({
loggedIn: true,
loaded: true
})
}
})
}
render() {
const { loggedIn, loaded } = this.state;
if (!loaded) {
return (
<View></View>
)
}
if (!loggedIn) {
return (
<View></View>
);
}
const onAuthStateChangedUnsubscribe =
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
// -> Alert Email Verification
await user.sendEmailVerification()
const onIdTokenChangedUnsubscribe =
firebase.auth().onIdTokenChanged((user) => {
const unsubscribeSetInterval = setTimeout(() => {
firebase.auth().currentUser.reload();
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true)
}, 10000);
if (user && user.emailVerified) {
clearInterval(unsubscribeSetInterval) //delete interval
onAuthStateChangedUnsubscribe() //unsubscribe onAuthStateChanged
// -> Go to your screnn
return onIdTokenChangedUnsubscribe() //unsubscribe
onIdTokenChanged
}
})
}
})
return (
<View></View>
)
}
}

Authentication flow react native

My app functions in a way that part of the app is visible without logging in, and to view the rest of it, users have to be signed in. My app consists of 2 stacks, the auth stack and the app stack. The auth stack contains the Login and Signup screens. Currently this is the logic of my app. For example, lets say the user goes the to Messages Tab which is only visible is the user is signed in. On MessagesScreen.js, I have the following code.
const [user, setUser] = useState();
useEffect(() => {
console.log('Use effect called');
if (!user) {
fetchUser();
if (!user) {
console.log('THis is called');
navigation.navigate('Auth', {
screen: 'Login',
params: {comingFromScreen: 'Messages'},
});
} else {
console.log(user);
}
}
}, []);
const fetchUser = async () => {
try {
const userData = await getUser();
setUser(userData);
} catch (e) {
console.log('No user found');
}
};
getUser, is the following function:
export const getUser = async () => {
try {
let userData = await AsyncStorage.getItem('userData');
let data = JSON.parse(userData);
} catch (error) {
console.log('Something went wrong', error);
}
};
And the LoginScreen consists of the following code:
const handleLogin = () => {
if (email === '' || password === '') {
alert('Email or password not provided');
} else {
firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then((res) => {
storeUser(JSON.stringify(res.user));
})
.catch((e) => alert(e.message));
navigation.navigate('Home', {screen: comingFromScreen});
}
};
storeUser is the following:
export const storeUser = async (user) => {
try {
await AsyncStorage.setItem('userData', JSON.stringify(user));
} catch (error) {
console.log('Something went wrong', error);
}
};
When I first navigate to the Messages Screen, the logic works and I get presented with the login screen. But if I click on the 'X' button on the login screen which takes me back to the home screen and then go back to the Messages Screen, I get presented with the screen and moreover, useEffect is not even called.
I'm a little new to react native so can someone tell me what I need to change to achieve my desired effect?
You could make the useEffect depend on the user state by doing the following and it will call every-time the user state changes.
It will always call useEffect as long as user changes like below:
useEffect(() => {
console.log('Use effect called');
if (!user) {
fetchUser();
if (!user) {
navigation.navigate('Auth', {
screen: 'Login',
params: {comingFromScreen: 'Messages'},
});
} else {
console.log(user);
}
}
}, [user]);
Found the solution to this problem, I used the useFocusEffect hook instead of useEffect and it seemed to solve the problem.

Screen redirection takes time to execute after asynchronous call in React-native

I am developing a small app with Expo, React-native-router-flux, firebase and react-redux. I am trying to implement a launch screen that appears after the splash screen and checks if the user is loaded or not. The launch screen calls the following action inside componentDIdMount function:
export const tryToSignInSilently = user => {
return () => {
console.log(user);
console.log(Actions);
setTimeout(() => {
if (user != null) Actions.tabbar();
else Actions.LoginScreen();
}, 1000);
};
};
I had to add that setTimeout to be able to redirect the screen otherwise, it would not change screen. 1) Is that the recommended solution to the problem?
After It redirects to the login screen and the submit button is pressed, another action is created:
export const login = (email, password) => {
return dispatch => {
dispatch({ type: LOGIN });
console.log("This executes");
FirebaseService.signIn(email, password)
.then(user => {
console.log("This takes almost a minute to execute");
dispatch({ type: LOGIN_SUCCESS, payload: user });
Actions.tabbar();
})
.catch(error => {
dispatch({ type: LOGIN_FAIL });
if (error) {
Alert.alert(
i18n.t("app.attention"),
i18n.t("login.enter.message"),
[{ text: i18n.t("app.ok") }],
{ cancelable: true }
);
}
}); };};
FirebaseService.signIn function =>
static async signIn(email, password) {
return await firebase.auth().signInWithEmailAndPassword(email, password); }
The interesting note is: If I press the submit button in the login screen, and save the code (causing the live reload), the firebase function is executed immediately and the page is correctly redirected to the home screen.
2) What could be causing that behavior?
Thank you very much!
Try to encapsulate your component with a using useContext hook approach.
Do all the login inside the context component by using useEffect hook with the Firebase function onAuthStateChanged. See sample code below:
const AuthProvider = ({ children }) => {
const [userObject, setUserObject] = useState(null);
const [loggedIn, setLoggedIn] = useState(null);
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if(user){
setLoggedIn(true);
setUserObject(user);
}
else {
setLoggedIn(false);
setUserObject(null);
}
});
// Cleanup subscription on unmount
return () => unsubscribe();
}, []);
const [state, dispatch] = useReducer(reducer, []);
return(
<AuthContext.Provider value={{ loggedIn, userObject }}>{ children }</AuthContext.Provider>
);
}
export { AuthProvider, AuthContext };
Then on the launch screen use the context variable 'loggedIn' to detect if the user is already loggedin or not.

Storing a value in Redux

I am building a React Native app, mainly for verifying tickets, to be used by event administrators. The back-end is served by a Laravel app with a working OAuth2-server. I have a working login against that server but now I need to store the access token, to request data, such as events, and to verify if a ticket is matched for a given event.
I'm trying to implement Redux to store the access token etc. The login form I have updates the store via actions correctly, but I can't get it to work with the access token.
Here is the login screen:
import React, { Component } from 'react';
import { Text, View, TextInput, Button } from 'react-native';
import { connect } from 'react-redux'
import StringifyBody from './../lib/oauth2/StringifyBody'
import { login, storeTokens } from '../redux/actions/auth.js'
class Login extends Component {
constructor (props) {
super(props);
this.state = {
route: 'Login',
loading: false,
email: '',
password: '',
accessToken: '',
};
}
handleClick (e) {
e.preventDefault();
return new Promise(function(resolve, reject) {
var data = StringifyBody(this.state.password, this.state.email)
// XHR settings
var xhr = new XMLHttpRequest()
xhr.withCredentials = true
xhr.onerror = function() {
reject(Error('There was a network error.'))
}
xhr.open("POST", "http://192.168.0.141/oauth/access_token")
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded")
xhr.send(data)
xhr.onloadend = function() {
if (xhr.status === 200) {
var parsedJson = JSON.parse(xhr.response)
responseArray = []
for(var i in parsedJson) {
responseArray.push([parsedJson [i]])
}
// assign values to appropriate variables
let accessToken = responseArray[0];
console.log('access token is: ' + accessToken)
accessToken => this.setState({ access_token: accessToken })
this.props.tokenStore(this.state.accessToken) // This doesn't work: "cannot read property 'tokenStore' of undefined"
resolve(xhr.response)
} else {
reject(Error('Whoops! something went wrong. Error: ' + xhr.statusText))
}
}
})
.done(() => {
this.props.onLogin(this.state.email, this.state.password); // This works
})
}
render() {
return (
<View style={{padding: 20}}>
<Text style={{fontSize: 27}}>{this.state.route}</Text>
<TextInput
placeholder='Email'
autoCapitalize='none'
autoCorrect={false}
keyboardType='email-address'
value={this.state.email}
onChangeText={(value) => this.setState({ email: value })} />
<TextInput
placeholder='Password'
autoCapitalize='none'
autoCorrect={false}
secureTextEntry={true}
value={this.state.password}
onChangeText={(value) => this.setState({ password: value })} />
<View style={{margin: 7}}/>
<Button onPress={(e) => this.handleClick(e)} title={this.state.route}/>
</View>
);
}
}
const mapStateToProps = state => {
return {
isLoggedIn: state.auth.isLoggedIn,
access_token: state.auth.access_token,
}
}
const mapDispatchToProps = (dispatch) => {
return {
onLogin: (email, password) => { dispatch(login(email, password)); },
tokenStore: (accessToken) => { dispatch(storeTokens(accessToken)) },
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Redux actions:
export const login = (email, password) => {
return {
type: 'LOGIN',
email: email,
password: password
};
};
export const logout = () => {
return {
type: 'LOGOUT'
};
};
export const storeTokens = () => {
return {
type: 'STORE_TOKENS',
access_token: accessToken,
}
}
And finally the reducers:
const defaultState = {
isLoggedIn: false,
email: '',
password: '',
access_token: '',
};
export default function reducer(state = defaultState, action) {
switch (action.type) {
case 'LOGIN':
return Object.assign({}, state, {
isLoggedIn: true,
email: action.email,
password: action.password
});
case 'LOGOUT':
return Object.assign({}, state, {
isLoggedIn: false,
email: '',
password: ''
});
case 'STORE_TOKENS':
return Object.assign({}, state, {
access_token: action.accessToken,
})
default:
return state;
}
}
I've also tried passing the data to this.props.storeTokens (the actual action) in a componentDidMount() which gives me the error undefined is not a function (evaluating 'this.props.storeTokens()') componentDidMount Login.js:57:8
My question then is: How do I store the variable I get from my XHR POST in the redux store? Why is this.props.tokenStore and this.props.storeToken not defined?
Hey thats a mistake owing to javascript concept. You are calling
this.props.tokenStore(this..state.accessToken) // This doesn't work: "cannot read property 'tokenStore' of undefined"
inside a function defined using ES5 syntax. either you store the reference of this outside the function in some variable and then use that variable instead of this. The other option is define arrow function instead. So change your function keyword into
() =>
and this should work. this as of now in your implementation doesn't point to component that you are thinking