I setup a basic auth system following the react-navigation auth flow guide with FeathersJS react-native client.
Here is my main index.tsx
file:
import 'react-native-gesture-handler';import React, { useReducer, useEffect, useMemo, ReactElement,} from 'react';import { Platform, StatusBar } from 'react-native';import { NavigationContainer } from '@react-navigation/native';import { AppLoading } from 'expo';import { useFonts } from '@use-expo/font';import SplashScreen from './screens/SplashScreen';import { AuthContext } from './contexts';import { client } from './utils';import DrawerNavigator from './navigation/DrawerNavigator';import LogonStackNavigator from './navigation/LogonStackNavigator';interface State { isLoading: boolean; isSignOut: boolean; userToken: string|null;}const App = (): ReactElement => { /* eslint-disable global-require */ const [fontsLoaded] = useFonts({'Lato-Regular': require('../assets/fonts/Lato-Regular.ttf'),'Lato-Bold': require('../assets/fonts/Lato-Bold.ttf'),'Poppins-Light': require('../assets/fonts/Poppins-Light.ttf'),'Poppins-Bold': require('../assets/fonts/Poppins-Bold.ttf'), }); /* eslint-enable global-require */ const [state, dispatch] = useReducer( (prevState: State, action): State => { switch (action.type) { case 'RESTORE_TOKEN': return { ...prevState, userToken: action.token, isLoading: false, }; case 'SIGN_IN': return { ...prevState, isSignOut: false, userToken: action.token, }; case 'SIGN_OUT': return { ...prevState, isSignOut: true, userToken: null, }; default: return prevState; } }, { isLoading: true, isSignOut: false, userToken: null, }, ); useEffect(() => { const bootstrapAsync = async (): Promise<void> => { let userToken; try { const auth = await client.reAuthenticate(); console.log('reAuthenticate:', auth); userToken = auth.accessToken; } catch (e) { // eslint-disable-next-line no-console console.log('reAuthenticate failure:', e); } dispatch({ type: 'RESTORE_TOKEN', token: userToken, }); }; bootstrapAsync(); }, []); const authContext = useMemo( () => ({ signIn: async (data) => { // In a production app, we need to send some data (usually username, password) to server and get a token // We will also need to handle errors if sign in failed // After getting token, we need to persist the token using `AsyncStorage` // In the example, we'll use a dummy token // eslint-disable-next-line no-console console.log('signIn', data); try { const auth = await client.authenticate({ strategy: 'local', ...data, }); console.log(auth); dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token', }); } catch (e) { console.log('signIn failure:', e); } }, signOut: async () => { try { await client.logout(); dispatch({ type: 'SIGN_OUT' }); } catch (e) { console.log('signOut failure:', e); } }, signUp: async (data) => { // In a production app, we need to send user data to server and get a token // We will also need to handle errors if sign up failed // After getting token, we need to persist the token using `AsyncStorage` // In the example, we'll use a dummy token // eslint-disable-next-line no-console console.log('signUp', data); dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token', }); }, }), [], ); if (!fontsLoaded) { return <AppLoading />; } if (state.isLoading) { return <SplashScreen />; } return (<AuthContext.Provider value={authContext}> {Platform.OS === 'ios'&& (<StatusBar barStyle="dark-content" /> )} {state.userToken == null ? (<NavigationContainer><LogonStackNavigator /></NavigationContainer> ) : (<NavigationContainer><DrawerNavigator /></NavigationContainer> )}</AuthContext.Provider> );};export default App;
And my SiginScreen.tsx
file which handle the login form:
import React, { ReactElement } from 'react';import { StyleSheet, KeyboardAvoidingView } from 'react-native';import { StackNavigationProp } from '@react-navigation/stack';import { RouteProp } from '@react-navigation/core';import { AuthContext } from '../contexts';import { LogonHeader, Button, Input, Text, TextLink,} from '../components';import { LogonStackParamList } from '../navigation/LogonStackNavigator';interface Props { navigation: StackNavigationProp<LogonStackParamList, 'SignIn'>; route: RouteProp<LogonStackParamList, 'SignIn'>;}const SignInScreen = ({ navigation }: Props): ReactElement => { const [email, setEmail] = React.useState(''); const [password, setPassword] = React.useState(''); const { signIn } = React.useContext(AuthContext); return (<KeyboardAvoidingView style={styles.container} behavior="padding"><LogonHeader title="Se connecter" /><Input placeholder="E-mail" value={email} onChangeText={setEmail} keyboardType="email-address" /><Input placeholder="Mot de passe" value={password} onChangeText={setPassword} secureTextEntry /><Button title="Connexion" onPress={() => signIn({ email, password, })} /></KeyboardAvoidingView> );};export default SignInScreen;
It works as expected, but I can't figure out how to handle the error case.
Currently, it's just a console.log
statement on index.tsx
file.
How can I properly informs the SignInScreen
component that the logins fail to show a message at the user end? Should I use redux or something?
More exactly: I would like to put an error text message directly on SignInScreen
in case of failure.