Authentication flow react native - 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.

Related

How to clean up React-Native useEffect with axios

Currently I have defined in a functional component a useEffect as below
useEffect(() => {
(async function () {
posts.current = await BlogConsumer.getBlogPosts();
setLoading(false);
})();
return () => {
BlogConsumer.call_controller.abort();
};
}, []);
where this BlogConsumer is defined as below
class BlogConsumer {
static posts = {};
static call_controller = new AbortController();
static async getBlogPosts() {
await axios
.get('https://nice.api', {
signal: this.call_controller.signal,
})
.then(response => {
// treatment for success
})
.catch(error => {
// treatment for erros
});
return this.posts;
}
}
export default BlogConsumer;
The overral ideia is that in the render of the component I'll be calling a static method from my consumer and will retrieve the necessary data. For the pourpuse of not having memory leaks, I have my callback function in my useEffect that will abort my call whenever I unmount the component, but this is not working. React's message of Warning: Can't perform a React state update on an unmounted component. still appears if I enter the component and leave the screen before the API call is finished. I don't know where I am wrong, so I'd like a little help.
Thanks in advance.
You could just cancel the request on unmount. Like this:
export const fetchData = async (signal) => {
try {
const res = await instance.get("/pages/home", {
signal,
});
return res.data;
} catch (error) {
return Promise.reject(error);
}
};
useEffect(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => {
controller.abort()
};
}, []);

navigation after dispatchin action redux with 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.

React Native: Unable to catch thrown error

I'm trying to catch an error from my signIn method and then display an alert in my code. I get a warning saying "Unhandled promise rejection..."
export default function Login({navigation}){
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const [showAlert, setShowAlert] = React.useState(false);
const [showAlert2, setShowAlert2] = React.useState(false);
const { signIn } = React.useContext(AuthContext);
const submit = async() => {
if (email === '' || password === '') {
setShowAlert(true);
} else {
signIn(email, password).catch(error =>
setShowAlert2(true)); // THIS NEVER GETS TRIGGERED, WHY?
}
}
...
}
signIn is defined in my App.js like this:
const authContext = React.useMemo(() => {
return {
signIn: (email, password) => {
auth().signInWithEmailAndPassword(email.email, password.password)
.then((res) => {
setIsLoading(false);
setUser(res.user.uid);
})
.catch(error => {
throw error; // This is the error that should be catched in "submit"
})
},
signUp: () => {
setIsLoading(false);
setUser("test");
},
signOut: () => {
setIsLoading(false);
auth().signOut().then(() => console.log('User signed out!'));
setUser(null);
}
};
}, []);
As you see I perform "throw error" at some point. That is the error I want to catch in my submit const above.
This is the error I get:
TypeError: undefined is not an object (evaluating 'signIn(email, password).catch')
You need to return that auth() call, and then remove the catch, then the error will be passed to whatever calls signIn
signIn: (email, password) => {
return auth() // add return here
.signInWithEmailAndPassword(email.email, password.password)
.then(res => {
setIsLoading(false);
setUser(res.user.uid);
})
},
You can even clean this up further by removing the curly braces and return. The arrow will return the next value automatically:
signIn: (email, password) =>
auth()
.signInWithEmailAndPassword(email.email, password.password)
.then(res => {
setIsLoading(false);
setUser(res.user.uid);
});
The error you're seeing is basically saying that it can't find a catch method on the returned value of signIn. This is true because in your version signIn does not return anything. The signIn function must return a promise (which is just an object with methods like then and catch); if you return a promise then it will have that catch method, that can then be called.

How can I make the user always logged in? 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

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.