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')}>
Related
I am implementing a provider which helps me to have the state of my user in different views, the main function of this provider is to render different one or the other stack navigator depending on whether the variable is full or empty, this in order to be able to generate two groups of screens depending on whether the user is authenticated or not.
Here my code:
View router.tsx:
import { NavigationContainer } from "#react-navigation/native"
import React, { useContext, useEffect, useState, useRef } from "react"
import { UserContext } from "./context/Usuario"
import AuthStack from "./routes/AuthStack"
import GeneralStack from "./routes/GeneralStack"
const Router = () => {
const { me } = useContext(UserContext)
const auth = useRef(false)
useEffect(() => {
return () => {
auth.current = me !== null
console.log("Hola")
}
}, [me])
return (
<NavigationContainer>
{auth.current ? <GeneralStack /> : <AuthStack />}
</NavigationContainer>
)
}
export default Router
Provider user,js:
import React, { useEffect, createContext, useState } from "react"
import AsyncStorage from "#react-native-async-storage/async-storage"
export const UserContext = createContext()
const UserProvider = ({ children }) => {
const [me, setMe] = useState(undefined)
const validStorage = async () => {
try {
const miSesion = await AsyncStorage.getItem("sesion")
console.log(miSesion)
setMe(JSON.parse(miSesion))
} catch (error) {
console.log(`ERROR: ${error.message}`)
}
}
useEffect(() => {
validStorage()
}, [])
return (
<UserContext.Provider value={{ me, setMe }}>
{children}
</UserContext.Provider>
)
}
export default UserProvider
GeneralStack:
import { createNativeStackNavigator } from "#react-navigation/native-stack"
import React from "react"
import TabStack from "./TabStack"
//import TabStack from "./TabStack"
const GeneralScreen = createNativeStackNavigator()
const GeneralStack = () => {
return (
<GeneralScreen.Navigator screenOptions={{ headerShown: false }}>
<GeneralScreen.Screen name="Tabs" component={TabStack} />
</GeneralScreen.Navigator>
)
}
export default GeneralStack
AuthStack:
import { createNativeStackNavigator } from "#react-navigation/native-stack"
import React from "react"
import Login from "../Screens/Login"
import Registro from "../Screens/Registro/Registro"
import SplashScreen from "../SplashScreen"
const AuthScreen = createNativeStackNavigator()
const AuthStack = () => {
return (
<AuthScreen.Navigator
initialRouteName="Splash"
screenOptions={{ headerShown: false }}>
<AuthScreen.Screen name="Login" component={Login} />
<AuthScreen.Screen name="Register" component={Registro} />
<AuthScreen.Screen name="Splash" component={SplashScreen} />
</AuthScreen.Navigator>
)
}
export default AuthStack
Login:
import React, { useState, useContext } from "react"
import {
Image,
ScrollView,
StatusBar,
Text,
TouchableOpacity,
View,
} from "react-native"
import { useNavigate } from "../../Hooks/useNavigate"
import MyTextInput from "../../components/MyTextInput"
import colors from "../../styles/colors"
import { loginStyles } from "../../styles/styles"
import { UserContext } from "../../context/Usuario"
import AsyncStorage from "#react-native-async-storage/async-storage"
export default function Login() {
const [, setIsSession] = useState(false)
const { setMe } = useContext(UserContext)
const navigate = useNavigate()
const [hidePassword, sethidePassword] = React.useState(false)
const [user] = useState({ user: "admin", password: "admin123" })
const [form, setForm] = useState({ user: "", password: "" })
const getStorage = async () => {
if (await AsyncStorage.getItem("sesion")) {
setIsSession(true)
} else {
setIsSession(false)
}
}
const signIn = async () => {
try {
console.log(user)
if (form.user === user.user && form.password === user.password) {
await AsyncStorage.setItem("sesion", JSON.stringify(form))
setMe(form)
setIsSession(true)
}
} catch (error) {
console.error(error)
}
}
const closeSesion = async () => {
await AsyncStorage.removeItem("sesion")
getStorage()
}
return (
<ScrollView contentContainerStyle={[loginStyles.container]}>
<StatusBar backgroundColor={colors.PURPLE} translucent={true} />
<View style={loginStyles.logo}>
<Image
source={require("../../recursos/images/Logo.png")}
style={{ height: 250, width: 250 }}
/>
</View>
<MyTextInput
onChangeText={(text: string) => {
setForm(state => ({ ...state, user: text }))
}}
keyboardType="email-address"
placeholder="E-mail"
/>
<MyTextInput
onChangeText={(text: string) => {
setForm(state => ({ ...state, password: text }))
}}
keyboardType={null}
placeholder="Contraseña"
bolGone={true}
secureTextEntry={hidePassword}
onPress={() => sethidePassword(!hidePassword)}
/>
<View style={loginStyles.btnMain}>
<TouchableOpacity onPress={signIn}>
<Text style={loginStyles.btntxt}>Iniciar Sesión</Text>
</TouchableOpacity>
</View>
<View style={loginStyles.btnTransparent}>
<TouchableOpacity
onPress={() => navigate({ screen: "Register" })}>
<Text
style={[loginStyles.btntxt, { color: colors.PURPLE }]}>
Registrarse
</Text>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity>
<Text
style={[
loginStyles.txtTransparent,
{ textDecorationLine: "none" },
]}>
Olvide mi contraseña
</Text>
</TouchableOpacity>
</View>
</ScrollView>
)
}
Home:
import AsyncStorage from "#react-native-async-storage/async-storage"
import React, { useState } from "react"
import { Image, ScrollView, TouchableOpacity } from "react-native"
import { Calendar } from "react-native-calendars"
import { Text } from "react-native-elements"
import { useNavigate } from "../../Hooks/useNavigate"
import colors from "../../styles/colors"
const Home = () => {
const [showModal, setShowModal] = useState(false)
const [date, setDate] = useState<string>()
const navigate = useNavigate()
const [isSession, setIsSession] = useState(false)
const getStorage = async () => {
const data = await AsyncStorage.getItem("sesion")
console.log(data)
if (data) {
setIsSession(true)
} else {
setIsSession(false)
}
}
const closeSesion = async () => {
await AsyncStorage.removeItem("sesion")
getStorage()
}
return (
<ScrollView>
<TouchableOpacity onPress={() => closeSesion()}>
<Text>Hola porque soy bien molon</Text>
</TouchableOpacity>
<Image
source={require("../../recursos/images/coralio_logo.png")}
style={{
marginTop: 40,
height: 110,
width: "80%",
justifyContent: "center",
alignSelf: "center",
}}
/>
<Calendar
theme={{
selectedDayBackgroundColor: colors.BLACK,
arrowColor: colors.WHITE,
monthTextColor: colors.WHITE,
}}
style={{
backgroundColor: colors.PURPLE,
borderRadius: 10,
elevation: 4,
marginTop: 60,
margin: 10,
height: 400,
}}
onDayPress={day => {
console.log(day.dateString)
setDate(day.dateString)
setShowModal(false)
}}
onMonthChange={() => {}}
initialDate={"2023-01-16"}
minDate={new Date()
.toLocaleDateString("es-US", {
year: "numeric",
month: "2-digit",
day: "numeric",
formatMatcher: "basic",
})
.split("/")
.reverse()
.join("-")}
markedDates={{
day: {
marked: true,
dotColor: colors.WHITE,
selected: true,
selectedColor: colors.PURPLE,
},
}}
//maxDate={"2023-12-31"}
//hideExtraDays={false}
//disableArrowLeft={true}
//disableArrowRight={true}
//hideArrows={true}
//hideDayNames={true}
/>
</ScrollView>
)
}
export default Home
The problem I have is when doing the close session in home, if in login it sends me from the authstack view to generalstack, when I do the close session it doesn't send me back to login, but it does clean the state of the variable from asyncstorage.
Help :(
It looks like you're not clearing the me variable in your context when the user ends the session. I think your closeSesion method should look like this:
const closeSesion = async () => {
setMe(null)
await AsyncStorage.removeItem("sesion")
getStorage()
}
i'am fairly new to react native and i'm doing a social media clone. I've got my navigator and the login via a database done , but i was wondering if there was a way to link a page/screen to another one by pressing a button .
Here is my code for the home screen after logging in :
import React, {Component} from 'react';
import {View, Text, FlatList} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
class HomeScreen extends Component {
constructor(props){
super(props);
this.state = {
isLoading: true,
listData: []
}
}
componentDidMount() {
this.unsubscribe = this.props.navigation.addListener('focus', () => {
this.checkLoggedIn();
});
this.getData();
}
componentWillUnmount() {
this.unsubscribe();
}
getData = async () => {
const value = await AsyncStorage.getItem('#session_token');
return fetch("http://localhost:3333/api/1.0.0/search", {
'headers': {
'X-Authorization': value
}
})
.then((response) => {
if(response.status === 200){
return response.json()
}else if(response.status === 401){
this.props.navigation.navigate("Login");
}else{
throw 'Something went wrong';
}
})
.then((responseJson) => {
this.setState({
isLoading: false,
listData: responseJson
})
})
.catch((error) => {
console.log(error);
})
}
checkLoggedIn = async () => {
const value = await AsyncStorage.getItem('#session_token');
if (value == null) {
this.props.navigation.navigate('Login');
}
};
render() {
if (this.state.isLoading){
return (
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}>
<Text>Loading..</Text>
</View>
);
}else{
return (
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<Text>Welcome to The app </Text>
</View>
);
}
}
}
export default HomeScreen;
Now ideally i would want a button in my else statement which could lead me to another screen (eg main screen of the app ) after logging in .
App.js :
import 'react-native-gesture-handler';
import React, { Component } from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createDrawerNavigator } from '#react-navigation/drawer';
import HomeScreen from './screens/home';
import LoginScreen from './screens/login';
import SignupScreen from './screens/signup';
import LogoutScreen from './screens/logout';
const Drawer = createDrawerNavigator();
class App extends Component{
render(){
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Login" component={LoginScreen} />
<Drawer.Screen name="Signup" component={SignupScreen} />
<Drawer.Screen name="Logout" component={LogoutScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
}
export default App;
If you're new to react native, I'd suggest that you give functional components and hooks a try, this is how your code would look like as a functional component after some clean up
(The answer is included in component)
import React, { Component, useEffect, useState } from "react";
import { View, Text, FlatList, Button } from "react-native";
import AsyncStorage from "#react-native-async-storage/async-storage";
const HomeScreen = (props) => {
const [isLoading, setIsLoading] = useState(true);
const [listData, setListData] = useState([]);
useEffect(() => {
const subscription = props.navigation.addListener("focus", () => {
checkLoggedIn();
});
getData();
return subscription.remove(); //similar to component unmount, syntax might be different based on your version
}, []);
const getData = async () => {
setIsLoading(true)
const value = await AsyncStorage.getItem("#session_token");
return fetch("http://localhost:3333/api/1.0.0/search", {
headers: {
"X-Authorization": value,
},
})
.then((response) => {
if (response.status === 200) {
return response.json();
} else if (response.status === 401) {
props.navigation.navigate("Login");
} else {
throw "Something went wrong";
}
})
.then((responseJson) => {
setIsLoading(false);
setListData(responseJson);
})
.catch((error) => {
console.log(error);
});
};
const checkLoggedIn = async () => {
const value = await AsyncStorage.getItem("#session_token");
if (value == null) {
props.navigation.navigate("Login");
}
};
return (
<>
{isLoading ? (
<View
style={{
flex: 1,
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
}}
>
<Text>Loading..</Text>
</View>
) : (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text>Welcome to The app </Text>
<Button
title="Next Page"
onPress={() => {
props.navigation.navigate("yourScreenName");
}}
/>
</View>
)}
</>
);
};
export default HomeScreen;
since you screens are
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Login" component={LoginScreen} />
<Drawer.Screen name="Signup" component={SignupScreen} />
<Drawer.Screen name="Logout" component={LogoutScreen} />
//you can add more screens here just make sure to import them first
//example
<Drawer.Screen name="Example" component={ExampleScreen} />
// name will be the name you use to navigate to that screen
// component should have the screen you imported
then you should pass one of those names to props.navigation.navigate() so the button would be
<Button
title="Next Page"
onPress={() => {
props.navigation.navigate("Login");
}}
/>
If you want to stick with a class component, then :
import {TouchableOpacity} from "react-native"
In the else part,
<TouchableOpacity style ={{width:100, height:100}} onPress={()=>{this.props.navigation.navigate("screenname")}}></TouchableOpacity>
TouchableOpacity is a blank space on the screen, which can be styled easily, and you have an onPress event. Basically it's like a better version of a button. You can also place other components inside the TouchableOpacity, and make the whole thing clickable
I am trying to store data in Reducers file by calling Action file from my
The login screen is implemented using function components.
I can't get the point where I was mistaken and why I can't able to call the action file from my login screen.
so please help me to fix this issue. Thanks in advance.
Here is some code of my files
Login.js
import React, { useState } from 'react';
import { View, Text, SafeAreaView, StyleSheet, Image, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import * as actions from '../Actions';
import { Images } from '../Helpers/imageConstants';
import { RFValue } from 'react-native-responsive-fontsize';
import {
heightPercentageToDP as hp,
widthPercentageToDP as wp,
} from 'react-native-responsive-screen';
import CheckBox from 'react-native-check-box';
import { TouchableHighlight } from 'react-native-gesture-handler';
import { InputText } from '../Components/inputText';
import { ButtonTouch } from '../Components/button';
import { SignInUser } from '../Actions/authAction';
import { showMessage } from 'react-native-flash-message';
const Login = ({ navigation }) => {
const [isCheck, ischecked] = useState(false);
const [userEmail, setUserEmail] = useState('');
const [userPassword, setUserPassword] = useState('');
const handleSubmitPress = () => {
if (!userEmail) {
showMessage({
message: 'Please Enter Your Email',
type: 'danger',
});
} else if (!userPassword) {
showMessage({
message: 'Please Enter Your Password',
type: 'danger',
});
} else {
console.log('comes in else');
SignInUser({ userEmail, userPassword });
}
};
return (
<SafeAreaView style={styles.mainContainer}>
<View style={{ flex: 1 }}>
<Image source={Images.imageLogo} style={styles.imgLogo} />
<View style={{ marginTop: 60 }}>
<InputText
Placeholder={'Email'}
PlaceholderTextcolor={'#000'}
onChangeText={(userEmail) => setUserEmail(userEmail)}
/>
</View>
<View style={{ marginTop: 20 }}>
<InputText
Placeholder={'Password'}
PlaceholderTextcolor={'#000'}
onChangeText={(userPassword) => setUserPassword(userPassword)}
SecureTextEntry={true}
/>
</View>
<View style={styles.checkPwdView}>
<CheckBox
isChecked={isCheck}
style={styles.checkVw}
rightText={'Remeber Me'}
rightTextStyle={styles.rightChekBoxText}
onClick={() => {
ischecked(!isCheck);
}}
/>
<TouchableOpacity style={styles.forgotPwdTouch}>
<Text style={styles.forgotText}> Forgot Password ?</Text>
</TouchableOpacity>
</View>
<View style={{ marginBottom: 60 }}>
<ButtonTouch onPress={handleSubmitPress} title={'Login'} />
</View>
<View style={styles.dontAcntVw}>
<TouchableHighlight>
<Text style={styles.dontAcntTxt}>Dont have an account? </Text>
</TouchableHighlight>
<TouchableOpacity>
<Text style={styles.signUpTxt}> Signup here</Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
);
};
export default Login;
let LoginComponent = connect(actions)(Login);
export { LoginComponent };
authAction.js
import { usersArrayData } from '../Helpers/usersArrayData';
import { LOGIN_SUCCESS } from '../Actions/type';
export const SignInUser = (email, password) => async (dispatch) => {
console.log('log :--', email, password);
var userData = { data: usersArrayData };
console.log('come inn');
usersArrayData.filter((item) => {
console.log('come inn22222', usersArrayData);
if (item.emailid === email && item.pass === password) {
console.log('userdata===>>>', userData);
return dispatch({ type: LOGIN_SUCCESS, payload: userData });
} else {
return false;
}
});
};
authReducer.js
import { LOGIN_SUCCESS } from "../Actions/type"
const INITIAL_STATE = {userData:[]}
const authReducer= (state = INITIAL_STATE, action) => {
switch (action.type) {
case LOGIN_SUCCESS:
return { ...state, userData: action.payload };
default:
return state;
}
};
export default authReducer ;
I think you may add SignInUser in object function dispatch like this
const mapDispatchToProps = {
SignInUser,
};
let LoginComponent = connect(null, mapDispatchToProps)(Login);
export { LoginComponent };
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.
I am now using React native navigation 5 and in my "Home component I have a setParams which works but I get that error:
"The action 'SET_PARAMS' with the payload {"params":{"company":"Marciello Resto 2"}} was not handled by any navigator."
I am on Android Emulator.
Thank you all for your help!
//App.js
import * as React from "react"
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native'
import { NavigationContainer } from '#react-navigation/native'
import { createStackNavigator } from '#react-navigation/stack'
import { createDrawerNavigator } from '#react-navigation/drawer'
import EtiquettesPictureContext from './Components/Contexts/EtiquettesPictureContext'
import RootStack from './Navigation/Navigation'
import Home from './Components/Layouts/Home'
import Etiquettes from './Components/Layouts/Etiquettes'
import TemperaturesFridges from './Components/Layouts/TemperaturesFridges'
import BonsLivraisons from './Components/Layouts/BonsLivraisons'
import EtiquettesTakePicture from "./Components/EtiquettesTakePicture"
import BonsLivraisonsTakePicture from "./Components/BonsLivraisonsTakePicture"
import DlcAlertTakePicture from "./Components/DlcAlertTakePicture"
import Notifications from "./Components/Layouts/Notifications"
import Settings from "./Components/Layouts/Settings"
import Welcome from "./Components/Layouts/Welcome"
import Login from "./Components/Layouts/Login"
import Signup from "./Components/Layouts/Signup"
import InitLogin from "./Components/Layouts/InitLogin"
import MesEtiquettes from "./Components/Layouts/MesEtiquettes"
import WhatRole from "./Components/WhatRole"
const AuthContext = React.createContext()
const Stack = createStackNavigator()
const Drawer = createDrawerNavigator()
export default function App({ navigation }) {
const [etiquettesPictureData, blPictureData, setEtiquettesPictureData, setBlPictureData] = React.useState("")
updateInitialData = (data) => {
setEtiquettesPictureData(data)
}
updateBlData = (data) => {
blPictureData(data)
}
const [state, dispatch] = React.useReducer(
(prevState, action) => {
switch (action.type) {
case 'RESTORE_TOKEN':
return {
...prevState,
userToken: action.isLogged,
isLoading: false,
};
case 'SIGN_IN':
return {
...prevState,
isSignout: false,
userToken: action.isLogged,
};
case 'SIGN_OUT':
return {
...prevState,
isSignout: true,
userToken: null,
};
}
},
{
isLoading: true,
isSignout: false,
userToken: null,
}
);
React.useEffect(() => {
// Fetch the token from storage then navigate to our appropriate place
const bootstrapAsync = async () => {
let isLogged;
try {
isLogged = await InitLogin.initLogin()
} 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', isLogged: isLogged.logged });
};
bootstrapAsync();
}, []);
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', isLogged: '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', isLogged: 'dummy-auth-token' });
},
}),
[]
)
if (state.isLoading) {
// We haven't finished checking for the token yet
return (
<View style={[styles.container, styles.horizontal]}>
)
}
function RootStackScreen() {
return (
<Stack.Navigator>
{state.userToken == false ? (
<>
<Stack.Screen name="Welcome" component={Welcome} />
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Signup" component={Signup} />
</>
) : (
<>
<Stack.Screen name="Home" component={Home} options={({ route }) => ({ title: route.params ? route.params.company : "" })} />
<Stack.Screen name="Etiquettes" component={Etiquettes} />
<Stack.Screen name="TemperaturesFridges" component={TemperaturesFridges} />
</>
)}
</Stack.Navigator>
)
}
function DrawerScreen() {
return (
<Drawer.Navigator>
<Drawer.Screen name="TemperaturesFridges" component={TemperaturesFridges} />
</Drawer.Navigator>
)
}
return (
<EtiquettesPictureContext.Provider
value={{
etiquette: etiquettesPictureData,
Bl: blPictureData,
updateInitialData: updateInitialData,
updateBlData: updateBlData
}}>
<AuthContext.Provider value={authContext}>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={RootStackScreen} />
</Drawer.Navigator>
</AuthContext.Provider>
</EtiquettesPictureContext.Provider>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center'
},
horizontal: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 10
}
})
//Home.jsimport * as React from 'react';
import mainStyleSheet from "../../Themes/main.js"
import { MaterialHeaderButtons, Item} from '../HeaderButtons';
import Ionicons from 'react-native-vector-icons/FontAwesome5';
import Company from '../Entities/Company';
import Notification from '../Entities/Notification'
import {
SafeAreaView,
ScrollView,
View,
Text,
TouchableHighlight
} from 'react-native';
export default function Home({ navigation}) {
React.useEffect(() => {
Company.getCompany().then(company => {
navigation.setParams({ company })
})
this.didBlurSubscription = navigation.addListener(
'didFocus',
() => {
Notification.countNotifications()
.then(resp => {
navigation.setParams({ notificationsCount: resp.length })
})
}
)
// returned function will be called on component unmount
return () => {
this.didBlurSubscription()
}
}, [])
const { navigate} = navigation;
return (
<TouchableHighlight style={[mainStyleSheet.buttonHomeContainer, { marginTop: 0 }]} onPress={() => navigate('Etiquettes')} >
<Ionicons name="tag" style={{ marginLeft: 20 }} color="white" size={30} />
Etiquettes produits
<TouchableHighlight style={mainStyleSheet.buttonHomeContainer} onPress={() => navigate('TemperaturesFridges')}>
<Ionicons name="thermometer-quarter" style={{ marginLeft: 20 }} color="white" size={30} />
Relevés de température
<TouchableHighlight style={mainStyleSheet.buttonHomeContainer} onPress={() => navigate('BonsLivraisons')}>
<Ionicons name="file-invoice" style={{ marginLeft: 20 }} color="white" size={30} />
Bons de livraisons
<TouchableHighlight style={mainStyleSheet.buttonHomeContainer} onPress={() => navigate('DownloadableDocuments')}>
<Ionicons name="file-image" style={{ marginLeft: 20 }} color="white" size={30} />
Documents à télécharger
)
}
I believe this issue has to do with calling navigation.setParams when the component is unmounting. According to the react navigation docs [0],
The setParams method lets us update the params (route.params) of the current screen
If the current screen is already unmounting when setParams gets called, then it would make sense that the navigator would not be able to handle it. Rework your logic so you are not setting params on blur.
[0] https://reactnavigation.org/docs/navigation-prop/#setparams