navigation after dispatchin action redux with react native - react-native

I try to introduce authentication react native app with redux, after dispatching the login action.The code in the Login Screen:
const dispatch = useDispatch();
const auth = useSelector(state => state.authentication);
const onSubmit = (data: {email: string; password: string}) => {
dispatch(loginUser(data));
auth.isAuth && navigation.navigate('Home');
};
The code of action login is :
export const loginUser = data => async dispatch => {
try {
const res = await axios({
method: 'post',
url: `${API_URl}/auth/login`,
data,
});
dispatch({type: LOGIN_SUCCESS, payload: res.data});
} catch (error) {
dispatch({type: GET_ERRORS, payload: error.response.data});
dispatch({type: LOGIN_FAILED, payload: error.response.data});
}
};
The code for reducer is above:
case LOGIN_SUCCESS:
case REGISTER_SUCCESS:
return {
...state,
user: payload.user,
isAuth: true,
message: null,
};
I want to navigate to another screen 'Home' but this is not done for the first time after dispatching login action although I have verified that the state is changed :
enter image description the console after press login button

You're calling onSubmit, which calls loginUser. At loginUser though, you have an API call which is async, which means that right after you call loginUser, auth.isAuth will always be false.
You need to make sure auth.isAuth === true on componentDidMount of your login screen, or use something like redux-observable to react to actions that are being dispatched and perform the navigation.

Related

how to get a user by id with a get request to express using react native?

I am trying to render One users info for the profile page in react native using express and axios.
This is the controller responsible for getting a user.
// GET ONE USER BY ID
module.exports.findOneSingleUser = (req, res) => {
User.findOne({ _id: req.params.id })
.then(oneSingleUser => res.json({ user: oneSingleUser }))
.catch(err => res.json({ message: 'Something went wrong', error: err }));
}
this is the code im using to make the axios request to the server and I am able to get all the users in the DB but I want to be able to render one user by id or the token that is stored for login and for login to persist which is working.
const ProfileInfo = (props) => {
const { navigation, route } = props
const authCtx = useContext(AuthContext);
const token= authCtx.token
const [userInfo, setUserInfo] = useState([]);
useEffect(() => {
axios.get(`http://localhost:3000/api/users/`)
.then(res => console.log(res.data))
.catch(err => console.log(err))
}, [])
this is the code in util folder that gets the token from the backend
import axios from 'axios'
import { useNavigation } from '#react-navigation/native';
const BASE_URL = 'http://localhost:3000'
// ! LOGIN FUNCTION
export async function authenticate(email,password ){
const token = await axios.post(BASE_URL + '/api/login',
{
email: email,
password: password,
},{ withCredentials: true }
);
return token;
}
// ! REGISTER NEW USER FUNCTION
export async function createUser(email, password) {
const token = await axios.post(BASE_URL + '/api/register',
{
email: email,
password: password,
},{ withCredentials: true }
);
return token;
}
this is the screen where I have the profile info component being used
import React,{useEffect} from 'react'
import ProfileInfo from '../components/Profile/ProfileInfo';
import Statistics from '../components/Profile/Statistics';
const ProfileScreen = (props) => {
const {navigation} = props
return (
<>
<ProfileInfo navigation={navigation}/>
<Statistics />
</>
)
}
export default ProfileScreen
How do or What do I need to pass into the url of the axios request to get the data for the user that is logged in? thanks in advance.
when I change the server side to
User.findOne({ token: req.params.token})
&
useEffect(() => {
axios.get(`http://localhost:3000/api/users/${token}`)
.then(res => console.log(res.data))
.catch(err => console.log(err))
}, [])
I get a user but it is only the first user in DB not the user that is logged in... not sure how to get the one user that is logged in.

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.

Expo Client + Firebase javascript SDK Authentication

I have been trying to develop an App with Expo Client + Firebase Javascript SDK. I am using redux(redux-thunk) to manage state. However I am having some problems to authenticate the user because every Firebase call I make, it just executes if I force the live reload of the app or if I tap anywhere.
import { Alert } from "react-native";
import { Actions } from "react-native-router-flux";
import {
LOGIN,
LOGIN_FAIL,
LOGIN_SUCCESS,
} from "./types";
import { FirebaseService } from "../services";
import i18n from "../i18n";
import * as Facebook from "expo-facebook";
import * as Google from "expo-google-app-auth";
export const login = (email, password) => {
return (dispatch) => {
console.log(email);
console.log(password);
dispatch({ type: LOGIN });
FirebaseService.signIn(email, password)
.then((user) => {
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 }
);
}
});
};
I called this action creator when a button is pressed and the FirebaseService.signIn(email, password) is
static async signIn(email, password) {
return await firebase.auth().signInWithEmailAndPassword(email, password);
}
firebase was initialized with the right credentials. To be clearer, dispatch({ type: LOGIN_SUCCESS, payload: user }); will only be dispatched if I tap the screen anywhere or if I cause the hot reload. Otherwise, nothing happens and the app stay on hold. Do you have any clue of what could it be?
I am running it in an Iphone via Expo Client.

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.

React Native/Jest TypeError: Cannot read property 'params' of undefined - testing with jest

I'm trying to create a test in an application with jest and this is some lines of my code:
import React, { Component } from 'react';
import {...} from 'react-native';
import jwt_decode from 'jwt-decode';
class CreateProduct extends Component {
constructor(props) {
super(props);
this.keyboardHeight = new Animated.Value(0);
this.imageHeight = new Animated.Value(199);
this.state = {
isButtonsHidden: false,
title: '',
price: '',
description: '',
isDialogVisible: false,
messageError: '',
};
}
_goBack = async () => {
const {state} = this.props.navigation;
var token = state.params ? state.params.token : undefined;
this.props.navigation.navigate('MyProducts', {token:token});
}
I want to test the navigation:
this.props.navigation.navigate('MyProducts', {token:token});
Now this is the attempt to test:
describe('Testing navigation', () =>{
let wrapper = null
const spyNavigate = jest.fn()
const props = {
navigation:{
navigate: spyNavigate
}
}
const params = {
token: 'randomToken'
}
beforeEach(() => {
wrapper = shallow(<CreateProduct {...props}/>)
wrapper.setState({params: params})
})
it('should test navigation', () => {
wrapper.instance()._goBack(params)
expect(spyNavigate).toHaveBeenCalled()
})
})
But I'm receiving this error.
I'm assuming that there is an error with the way I'm passing the const params. Can you help me telling what's the best way I can do this to simulate a token and that way I can navigate in the screen?
Thanks.
Rootcause is your _goBack is async. But you don't await till it ends before running expect. Even more: jest also does not wait _goBack to finish so you don't even see an error
Cannot read property 'params' of undefined
that happens because you don't mock state in navigation.params.
To work with async code there are 2 different approaches in Jest: either returning Promise from the it() or running done() callback manually(it's passed as 1st argument in it()).
I'll picking 2nd since it allows us also await until goBack is finished before running expect:
describe('Testing navigation', () => {
let wrapper = null
const spyNavigate = jest.fn()
const props = {
navigation: {
navigate: spyNavigate,
state: {}
}
}
const params = {
token: 'randomToken'
}
beforeEach(() => {
wrapper = shallow(<CreateProduct {...props} />)
wrapper.setState({ params: params })
})
it('should test navigation', async () => {
await wrapper.instance()._goBack(params)
expect(spyNavigate).toHaveBeenCalled()
})
})
Or without using async/await it would look like
it('should test navigation', () => {
return wrapper.
instance()._goBack(params).
then(() => expect(spyNavigate).toHaveBeenCalled());
})
that looks messy
Or using done() callback
it('should test navigation', (done) => {
wrapper.
instance()._goBack(params).
then(() => expect(spyNavigate).toHaveBeenCalled()).
then(done);
})