Passing React Navigation v5 authentication errors back to signup/in screen - javascript

I am using React Navigation v5. I have an auth setup like their example in the docs. My problem is that I cannot work out how to pass error messages back to the component when there is a problem with signup/in.
The validation is done on the server and returned in the response object.
Currently the page just sits there with the loading spinner spinning while it waits for a response which it never receives.
Im going to try to cut down the amount of code I include here because it is basically a copy paste job from the docs
// Login.tsx
const loginUser = async () => {
setIsLoading(true);
signIn({ email, password });
};
return (
<View>
<TextInput
label='Email'
onChangeText={(text: string) => setEmail(text)}
value={email}
/>
<TextInput
label='Password'
onChangeText={(text: string) => setPassword(text)}
value={password}
secureTextEntry
/>
{errorMessage && <Text>{errorMessage}</Text>}
<Button mode='contained' disabled={showButton()} onPress={loginUser}>
{isLoading ? <ActivityIndicator color='white' /> : 'Submit'}
</Button>
<Button mode='outlined' onPress={() => navigation.navigate('SignUp')}>
Go to sign up
</Button>
</View>
);
// AuthStackNavigator.tsx
import React from 'react';
import { createStackNavigator } from '#react-navigation/stack';
import SignupScreen from '../Screens/SignupScreen';
import LoginScreen from '../Screens/LoginScreen';
const AuthStack = createStackNavigator();
export const AuthStackScreen = () => {
return (
<AuthStack.Navigator screenOptions={{ headerLeft: null }}>
<AuthStack.Screen name='SignUp' component={SignupScreen} />
<AuthStack.Screen name='SignIn' component={LoginScreen} />
</AuthStack.Navigator>
);
};
export default AuthStackScreen;
// Navigation.tsx
const authContext = useMemo(
() => ({
signIn: async ({ email, password }: SignInTypes) => {
const userData = {
email,
password,
};
API.post
.userLogin(userData)
.then((result) => {
if (
result.data.status === 'fail' ||
result.data.status === 'error'
) {
setIsLoading(false);
// I NEED TO DO SOMETHING HERE I THINK BUT ICANT WORK OUT WHAT I NEED TO DO
}
const accessToken = [
'accessToken',
result.data.data.tokens.accessToken.jwtToken,
];
const refreshToken = [
'refreshToken',
result.data.data.tokens.refreshToken.token,
];
addUser({
id: result.data.data.user._id,
email: result.data.data.user.email,
profileName: result.data.data.user.profileName,
balance: result.data.data.user.balance,
});
try {
AsyncStorage.multiSet([accessToken, refreshToken]);
setIsLoading(false);
return dispatch({ type: 'SIGN_IN', token: accessToken[1] });
} catch (e) {
return console.log('ASYNC STORAGE ERROR', e);
}
})
.catch((e) => {
console.log('Sign in error', e);
});
}
}),
[],
);
const navigationOptions = () => {
if (isLoading) {
return <LoadingScreen />;
}
return !state.userToken ? (
<AuthStackScreen />
) : (
<AppStackScreen />
);
};
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>{navigationOptions()}</NavigationContainer>
</AuthContext.Provider>
);

Create two separate Stack navigators. AuthStack and MainStack. Like you have.
Conditionally render those stacks based on auth Status. Like you have.
// AuthStack containing the Login and SignUp screens.
export const AuthStack = () => {
return (
<Stack.Navigator headerMode="none">
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</Stack.Navigator>
);
};
// Main Stack that contains The rest of the screens and other navigation components.
export const MainStack = () => {
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={TabNavigator} />
<Stack.Screen name="Ledger" component={LedgerScreen} />
<Stack.Screen name="CustomerProfile" component={CustomerProfileScreen} />
</Stack.Navigator>
// App.tsx or which ever file you choose to wrap your Stacks in the Navigation container.
const App = (props) => {
const isAuth = useSelector((state) => !!state.auth.token); // Comes from the redux store
return (
<NavigationContainer>
{isAuth && <MainStack />}
{!isAuth && <AuthStack />}
</NavigationContainer>
);
};
export default App
Here, I'm using react-redux to get the isAuth variable that holds the token which verifies if the user is authenticated or not. But, you can use the useContext hook to achieve this as well.
Based on the user's login status the appropriate stacks are rendered.
You can now make your calls to API (the ones you are making in Navigation.tsx) from within the particular screen components itself inside the useEffect and useCallback hooks and get back the responses within the same component, which can include error messages.
Store those error messages in useState or useReducer hooks and display these errors to the user under the appropriate TextInputs or use an Alert Box to display them.
There is no need to pass props between the screen components and React Navigation 5 to get the error messages, if you make your api calls in the screen component itself.

Related

React Navigation after login navigate problem

I'm using useNavigation in React Navigation. After logging in with AsyncStorage, I want to redirect to the home page, but I cannot go back to the App function. When I want to go to HomePage, Navigate cannot find it and returns an error.
How can I trigger a function named app?
Navigate Error (console)
The action 'NAVIGATE' with payload {"name":"HomeScreen"} was not
handled by any navigator
Login Code
await AsyncStorage.setItem("userData", JSON.stringify(data.data));
navigation.navigate('HomeScreen');
App.js
const App = () => {
const [signedIn, setSignedIn] = useState([]);
useEffect(() => {
_checkUser();
}, []);
async function _checkUser(){
let responseUser = await AsyncStorage.getItem("userData");
setSignedIn(responseUser);
}
if(signedIn != null) {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="HomeScreen" component={_homeScreen} />
</Stack.Navigator>
</NavigationContainer>
)
} else {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="WelcomeScreen" component={_welcomeScreen} />
<Stack.Screen name="RegisterScreen" component={_registerScreen} />
<Stack.Screen name="LoginScreen" component={_loginScreen} />
<Stack.Screen name="ForgotScreen" component={_forgotScreen} />
<Stack.Screen name="EmailCodeScreen" component={_emailCodeScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
}
When I try to do it with MOBX..
let signedIn = userStore.isLogin;
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}}>
{
signedIn ? (
<Stack.Screen name="HomeScreen" component={_homeScreen} />
) : (
<>
<Stack.Screen name="WelcomeScreen" component={_welcomeScreen} />
<Stack.Screen name="RegisterScreen" component={_registerScreen} />
<Stack.Screen name="LoginScreen" component={_loginScreen} />
<Stack.Screen name="ForgotScreen" component={_forgotScreen} />
<Stack.Screen name="EmailCodeScreen" component={_emailCodeScreen} />
</>
)
}
</Stack.Navigator>
</NavigationContainer>
)
Router
let data = await axios.post(baseURL+"users_checkMail", sendData);
uaStore.userStore.userData = data.data;
uaStore.userStore.isLogin = true;
await AsyncStorage.setItem("userData", JSON.stringify(data.data));
console.log(userStore.isLogin); // LOG: TRUE
setLoader(false);
navigation.navigate('HomeScreen');
If you are using mobx for state management, create and manage signedIn state in your mobx state tree. when user logs in , you just have to set signedIn state to true. When user logs out, set signedIn to false. No need to use navigation props. When you change your signedIn state, react-navigation component will rerender.
From the docs:
It's important to note that when using such a setup, you don't need to manually navigate to the Home screen by calling navigation.navigate('Home') or any other method. React Navigation will automatically navigate to the correct screen when isSigned in changes - Home screen when isSignedIn becomes true, and to SignIn screen when isSignedIn becomes false. You'll get an error if you attempt to navigate manually.
https://reactnavigation.org/docs/auth-flow/
So just remove the line:
navigation.navigate('HomeScreen');
And replace it with:
setSignedIn(true)
Or the MobX equivalent if you use MobX.
You can try like this
return (
<NavigationContainer>
{signedIn != null ? (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="HomeScreen" component={_homeScreen} />
</Stack.Navigator>
) : (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="WelcomeScreen" component={_welcomeScreen} />
<Stack.Screen name="RegisterScreen" component={_registerScreen} />
<Stack.Screen name="LoginScreen" component={_loginScreen} />
<Stack.Screen name="ForgotScreen" component={_forgotScreen} />
<Stack.Screen name="EmailCodeScreen" component={_emailCodeScreen} />
</Stack.Navigator>
)}
</NavigationContainer>
);
First of all you cant use NavigationContainer twice..I would suggest make two Navigator like this one Login and another AppStack like this! I would just use redux and redux persist with async storage,makes life easier
const App = () => {
useEffect(() => {
setTimeout(() => {
RNBootSplash.hide({fade: true});
}, 1000);
}, []);
const LoginStack = () => {
return (
<Stack.Navigator>
<Stack.Screen
name="Login"
component={Login}
options={{
headerTransparent: true,
cardStyle: {backgroundColor: colors.BACKGROUND},
...TransitionPresets.ModalSlideFromBottomIOS,
headerTitleStyle: {color: 'transparent'},
}}
/>
<Stack.Screen
name="Terms"
component={Terms}
options={{
headerTransparent: true,
cardStyle: {backgroundColor: colors.BACKGROUND},
...TransitionPresets.ModalPresentationIOS,
headerTitleStyle: {color: 'transparent'},
}}
/>
</Stack.Navigator>
);
};
const AppStack = () => {
return (
<Stack.Navigator>
{screens.map((screen, i) => (
<Stack.Screen
name={screen.name}
component={screen.screenname}
key={i}
options={{
headerTransparent: true,
cardStyle: {backgroundColor: colors.BACKGROUND},
...TransitionPresets.ModalSlideFromBottomIOS,
headerTitleStyle: {color: 'transparent'},
}}
/>
))}
</Stack.Navigator>
);
};
const AppState = () => {
const checkLoggedIn = useSelector((state) => state.AuthReducer.loggedIn);
return <>{checkLoggedIn === false ? <LoginStack /> : <AppStack />}</>;
};
return (
<Provider store={store}>
<NavigationContainer>
<AppState />
</NavigationContainer>
</Provider>
);
};
store.js
import {applyMiddleware, createStore} from 'redux';
import thunk from 'redux-thunk';
import AsyncStorage from '#react-native-async-storage/async-storage';
import {persistStore, persistReducer} from 'redux-persist';
import rootReducer from './index';
// Middleware: Redux Persist Config
const persistConfig = {
// Root
key: 'root',
// Storage Method (React Native)
storage: AsyncStorage,
// Whitelist (Save Specific Reducers)
whitelist: ['StickerReducer', 'AuthReducer'],
// Blacklist (Don't Save Specific Reducers)
blacklist: [''],
};
// Middleware: Redux Persist Persisted Reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
const middleware = [thunk];
const store = createStore(persistedReducer, applyMiddleware(...middleware));
// Middleware: Redux Persist Persister
let persistor = persistStore(store);
// Exports
export {store, persistor};
Auth Reducer
import {ADD_DEVICE_TOEKN, LOGGED_IN} from './types';
const initialState = {
loggedIn: false,
user_Id: '',
device_token: '',
};
export default AuthReducer = (state = initialState, action) => {
switch (action.type) {
case LOGGED_IN:
const paylodLoggedIn = action.payload;
return {
...state,
loggedIn: paylodLoggedIn.loggedIn,
user_Id: paylodLoggedIn.loggedIn,
};
case ADD_DEVICE_TOEKN:
const paylodToken = action.payload;
return {
...state,
device_token: paylodToken,
};
default:
return state;
}
};
import {combineReducers} from 'redux';
import AuthReducer from './AuthReducer';
import StickerReducer from './StickerReducer';
export default combineReducers({
AuthReducer: AuthReducer,
StickerReducer: StickerReducer,
});
Authaction.js
import {LOGGED_IN} from '../types';
export const LoginAction = (userData) => {
return (dispatch) => {
dispatch({
type: LOGGED_IN,
payload: userData,
});
};
};
Finally login dispacther
import {useDispatch} from 'react-redux';
import {LoginAction} from '../../../redux/actions/LoginAction';
const Login = ({navigation}) => {
const dispatch = useDispatch();
return (
<Pressable onPress={() =>dispatch(LoginAction(data))}>
</Pressable>
);
};
If you wanna trigger the App.js after successfull login ... then You need to wrap your NavigationContainer with React.Context
Your signedIn user is an object I think ... not an array
so by initalizing it in state with [] ... if(signedIn != null) will never evaluate to true ++ Make sure you JSON.parse your object after fetching it from AsyncStorage ...
const AuthContext = React.createContext({
signedIn,
_checkUser: () => {},
});
export const useAuth = () => React.useContext(AuthContext);
const defaultValue = {
signedIn,
_checkUser,
};
return (
<AuthContext.Provider value={defaultValue}>
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{signedIn ? (
<Stack.Screen name="HomeScreen" component={_homeScreen} />
) : (
<>
<Stack.Screen name="WelcomeScreen" component={_welcomeScreen} />
<Stack.Screen name="RegisterScreen" component={_registerScreen} />
<Stack.Screen name="LoginScreen" component={_loginScreen} />
<Stack.Screen name="ForgotScreen" component={_forgotScreen} />
<Stack.Screen
name="EmailCodeScreen"
component={_emailCodeScreen}
/>
</>
)}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
In LoginScreen
// Then you refresh you auth user in LoginScreen like
const { _checkUser } = useAuth();
_checkUser();
I had the same error when trying to use the useNavigation hook... I was able to resolve it using the dispatch method...
import { CommonActions } from '#react-navigation/native';
navigation.dispatch(
CommonActions.navigate({ name: 'Profile', params: { user: 'jane' }})
);
I created a utility function for less repetition...
const handleNavigate = (key: string) => {
navigation.dispatch(CommonActions.navigate({ name: key }));
};
// Usage
handleNavigate("Home")
This got rid of the error for me. Hope this helps

On first launch choose language React Native

I'm new to react native and using I18n to translate my multi language app according to user choice.
My main goal here is to show screen on first entry to the app that will decide the user language by his choice.
import React from 'react';
import Login from './login.js';
import Register from './register.js';
import Dashboard from './dashboard.js';
import { NavigationContainer, useNavigation } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { Text, View, Button } from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
const HAS_LAUNCHED = 'hasLaunched';
const ENGLISH = 'en';
const HEBREW = 'he';
function setAppLaunched(en) {
const navigation = useNavigation();
AsyncStorage.setItem(HAS_LAUNCHED, 'true');
AsyncStorage.setItem(en ? ENGLISH : HEBREW, 'true');
navigation.navigate('Login');
}
async function checkIfFirstLaunch() {
try {
const hasLaunched = await AsyncStorage.getItem(HAS_LAUNCHED);
if (hasLaunched === null) {
return (
<View>
<Text>Choose Language</Text>
<Button onPress={() => setAppLaunched(false)} title="Hebrew"/>
<Button onPress={() => setAppLaunched(true)} title="English"/>
</View>
);
}
return false;
} catch (error) {
return false;
}
}
console.log(checkIfFirstLaunch());
const Stack = createStackNavigator();
export default function App() {
return (
<>
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Register" component={Register} />
<Stack.Screen name="Dashboard" component={Dashboard} />
</Stack.Navigator>
</NavigationContainer>
</>
);
}
Once checkIfFirstLaunch() is triggered AsyncStorage saves the user language and remember it to the next times he launches the app.
I have created the syntax that will detect if user launched the app for the first time, but how can I actually show that screen?
How to display first launch screen using functional components?
EDIT
export default function App() {
return (
<>
<CheckIfFirstLaunch/>
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Register" component={Register} />
<Stack.Screen name="Dashboard" component={Dashboard} />
</Stack.Navigator>
</NavigationContainer>
</>
);
}
If I try that it returns the following
Error: Objects are not valid as a React child (found: object with keys
{_U, _V, _W, _X}). If you meant to render a collection of children,
use an array instead.
The error is because you're rendering a Functional Component that you defined as an async function.
Now I don't know if you want to show the "Choice" and the navigation screens at the same time. If you want that, maintain the structure you have and do this.
function checkIfFirstLaunch() {
const [selected, setSelected] = useState(false);
if (selected) return null;
const verifyHasLaunched = async () => {
try {
const hasLaunched = await AsyncStorage.getItem(HAS_LAUNCHED);
setSelected(hasLaunched != null)
} catch (err) {
setSelected(false);
}
}
useEffect(verifyHasLaunched);
const selectLaunched = (value) => {
setAppLaunched(value);
setSelected(true);
};
return (
<View>
<Text>Choose Language</Text>
<Button onPress={() => selectLaunched(false)} title="Hebrew"/>
<Button onPress={() => selectLaunched(true)} title="English"/>
</View>
);
}
If you need to change the render as in render either the "Choose screen" or the Navigation component
Then you have to change both App and checkIfFirstLaunch;
function checkIfFirstLaunch({ onSelect }) {
const selectLaunched = (value) => {
setAppLaunched(value);
onSelect();
};
return (
<View>
<Text>Choose Language</Text>
<Button onPress={() => selectLaunched(false)} title="Hebrew"/>
<Button onPress={() => selectLaunched(true)} title="English"/>
</View>
);
}
export default class App extends React.Component {
state = {
selected: false;
};
async componentDidMount() {
try {
const hasLaunched = await AsyncStorage.getItem(HAS_LAUNCHED);
this.setState({ selected: hasLaunched != null })
} catch (err) {
// Something went wrong
}
}
render() {
if (!this.state.selected) return <CheckIfFirstLaunch onSelect={() => this.setState({ selected: true })} />
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Register" component={Register} />
<Stack.Screen name="Dashboard" component={Dashboard} />
</Stack.Navigator>
</NavigationContainer>
);
}
}

React Native passing params in route getting undefined

I am trying to send param from screen A to screen B like this:
this is what I am trying so far:
await Auth.signIn(email, password)
.then((user) => {
navigation.navigate('SingInConfirm', {user: user});
})
And on my screen B, I want to recibe user params like this..
const SingInConfirmation = ({ route, signIn: signInCb }) => {
const { user } = route.params;
But I am getting this error: TypeError: undefined is not an object (evaluating 'route.params')
and this is my StackNavigator
const AuthStack = createStackNavigator();
const AuthModalStack = createStackNavigator();
const AuthNavigator = ({ signIn }) => (
<AuthModalStack.Navigator mode="modal" headerMode="none">
<AuthModalStack.Screen name="AuthPages">
{() => (
<AuthStack.Navigator>
<AuthStack.Screen name="SignIn" component={SignInScreen} />
</AuthStack.Navigator>
)}
</AuthModalStack.Screen>
<AuthModalStack.Screen name="SingInConfirm">
{({ navigation }) => <SingInConformation signIn={signIn} navigation={navigation} />}
</AuthModalStack.Screen>
</AuthModalStack.Navigator>
);
export default AuthNavigator;
Why I can't recibe route params on my screen B?
SingInConformation component doesn't receive route in this line as a prop, you need to provide it, change this line from :
{({ navigation }) => <SingInConformation signIn={signIn} navigation={navigation} />}
To this:
{({ route, navigation }) => <SingInConformation signIn={signIn} navigation={navigation} route={route} />}

How to reset user history with logout in react navigation react navigation?

I'm using react navigation and Firebase for authentication for a user to move to different screens in my react navigation app.
I created a logout function that signs the user out and takes them to the login screen. However on the upper left corner my app gives the user the option to go back into app without the need to log back in with button labelled as the name of the screen they just logged out of. Is there a way to disable this?
Here is my logout screen :
export default function ProductScreen({ navigation }) {
const logOutPress = () => {
try {
auth()
.signOut()
.then(() => {
navigation.navigate("Login"), alert("You have signed out");
});
} catch (error) {
console.log("err", error);
}
};
return (
<TouchableOpacity onPress={() => logOutPress()}>
<Text style={styles.buttonText}>Log Out</Text>
</TouchableOpacity>
);
}
Also here is my app.js where I house my react navigation and Firebase :
const Stack = createStackNavigator();
export default function App() {
const [loading, setLoading] = useState(true);
const [user, setUser] = useState(null);
useEffect(() => {
const usersRef = firebase.firestore().collection("users");
firebase.auth().onAuthStateChanged((user) => {
if (user) {
usersRef
.doc(user.uid)
.get()
.then((document) => {
const userData = document.data();
setLoading(false);
setUser(userData);
})
.catch((error) => {
setLoading(false);
});
} else {
setLoading(false);
}
});
}, []);
if (loading) {
return <></>;
}
return (
<NavigationContainer>
<Stack.Navigator>
{user ? (
<Stack.Screen name="Products">
{(props) => <ProductScreen {...props} extraData={user} />}
</Stack.Screen>
) : (
<>
<Stack.Screen name="Login" component={LoginScreen} />
</>
)}
<Stack.Screen name="Registration" component={RegistrationScreen} />
<Stack.Screen name="Payment" component={PaymentScreen} />
<Stack.Screen name="Lawn Care" component={LawnCareScreen} />
<Stack.Screen name="Reset Password" component={ResetPasswordScreen} />
<Stack.Screen name="Car Detail" component={CarDetailScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
To disable user from navigating back to the previous screen after logout you need to clear the stack of the stack navigator in the Product Screen . I have provided the code sample below
import { StackActions, NavigationActions } from "react-navigation";
export default function ProductScreen({ navigation }) {
const logOutPress = () => {
try {
auth()
.signOut()
.then(() => {
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: "Login" })],
});
navigation.dispatch(resetAction);
alert("You have signed out");
});
} catch (error) {
console.log("err", error);
}
};
return (
<TouchableOpacity onPress={() => logOutPress()}>
<Text style={styles.buttonText}>Log Out</Text>
</TouchableOpacity>
);
}
i use this code in the index.js to logout :
<Stack.Screen name="Main" component={Main} options={({ navigation,route }) => ({
title: '', headerLeft: () => (<TouchableOpacity onPress={() => (_singout())}
style={{ margin: 16, marginTop: Platform.OS == 'ios' ? StatusBar.currentHeight : 0 }}>
<Image resizeMode="contain" source={mylogoutImage} style={{ marginTop: 20 }} />
</TouchableOpacity>),
})} />
and in _singout() function clear my asyncStorage.
I hope to help you.

Navigation.navigate does not work in react navigation

I am creating an app and want to create a user authentication page. I followed the tutorial on the authentication flow page of the react navigation website, but now I whenever I try to navigate to the signup page from the login screen, I get the warning "Cannot update a component from inside the function body of a different component".
Here is my App.js:
import React, { useState } from 'react';
import {
FlatList,
} from 'react-native';
import { NavigationContainer, useNavigation } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import LoginScreen from "./Login";
import Home from "./Home";
import Signup from "./Signup";
import { AsyncStorage } from 'react-native'
export const COMPANY_NAME = "myCompany";
export const AuthContext = React.createContext();
const App: () => React$Node = () => {
const [state, dispatch] = React.useReducer(
(prevState, action) => {
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,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: null,
}
);
const authContext = React.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
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
signOut: () => dispatch({ type: 'SIGN_OUT' }),
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
dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
},
}),
[]
);
React.useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let userToken;
try {
userToken = await AsyncStorage.getItem('userToken');
} catch (e) {
// Restoring token failed
}
// After restoring token, we may need to validate it in production apps
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
dispatch({ type: 'RESTORE_TOKEN', token: userToken });
};
bootstrapAsync();
}, []);
return (
<NavigationContainer>
<AuthContext.Provider value={{authContext}}>
<RootStack state={state} authContext={authContext}/>
</AuthContext.Provider>
</NavigationContainer>
);
};
function RootStack(props) {
const Stack = createStackNavigator();
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
{props.state.userToken == null ? (
<>
<Stack.Screen
name="Login"
component={LoginScreen}
/>
<Stack.Screen
name="Signup"
component={Signup}
/>
</>
) : (
<Stack.Screen
name="Home"
component={Home}
/>
)
}
</Stack.Navigator>
);
}
Here is my Login.js:
import {StatusBar, Text, TextInput, TouchableOpacity, View, Button, TouchableHighlight} from "react-native";
import styles, {MAIN_COLOR} from "./Styles";
import React, {useState} from "react";
import { COMPANY_NAME, AuthContext } from "./App";
import { useNavigation } from '#react-navigation/native'
import TouchableItem from "#react-navigation/stack/src/views/TouchableItem";
export default function LoginScreen({ navigation }) {
//Must be a child of AuthContext.Provider
const [username, setUsername] = useState();
const [password, setPassword] = useState();
const { authContext } = React.useContext(AuthContext);
console.log(AuthContext)
return (
<View style={styles.container}>
<StatusBar
backgroundColor={MAIN_COLOR}
/>
<Text style={styles.welcome}>Welcome to {COMPANY_NAME}!</Text>
<TextInput
style={styles.input}
placeholder='Username'
onChange={(text) => setUsername(text)}
/>
<TextInput
style={styles.input}
placeholder='Password'
secureTextEntry
onChange={(text) => setPassword(text)}
/>
<View style={styles.btnContainer}>
<TouchableOpacity style={styles.usrBtn}
onPress={() => navigation.navigate('Signup')}>
<Text style={styles.btnText}>Sign Up</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.usrBtn}
onPress={() => authContext.signIn({username, password})}>
<Text style={styles.btnText}>Login</Text>
</TouchableOpacity>
</View>
</View>
);
}
Found the answer. In my Signup.js I had a component like
<TouchableOpacity style={styles.usrBtn}
onPress={navigation.navigate('Login')}>
That needed to be changed to
<TouchableOpacity style={styles.usrBtn}
onPress={() => navigation.navigate('Login')}>

Categories

Resources