React Native - How to update screen on an already mounted component - react-native

I have 2 screens:
Home Screen
Login Screen
My entry point is my Home screen, if I am logged in (fetching datas), log out and then log in to another account. Since my Home component is already mounted, my data will not be updated and this.getDatas() will not be called again. I can't find solutions, I think I need to use "ComponentWillUpdate" in my Home.js to call this.getDatas() or something like that but you can't find the solution.
Here's my code:
Home.js
componentDidMount = async () => {
const { navigation } = this.props;
if (!await AsyncStorage.getItem('auth')) {
navigation.navigate('Login');
} else {
this.getDatas();
}
}
getDatas() {
axios.get(`${apiUrl}/me`).then((res) => {
this.setState({
datas: res.data.datas,
});
}).catch((err) => {
console.warn(err);
});
}
Login.js
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: null,
password: null,
};
}
logIn() {
const { email, password } = this.state;
axios.post(`${apiUrl}/auth`, {
email,
password
}).then((res) => {
navigation.navigate('Home');
}).catch((err) => {
console.warn('Error:', err);
});
}
}
App.js
const MainNavigator = createStackNavigator({
Home,
CreateGame,
GamePreview,
CardSelection,
BonusCard,
Game,
GameResult
}, { initialRouteName: 'Home', headerMode: 'none', });
const LoginNavigator = createStackNavigator({
Login,
Subscribe,
SubscribeNext,
ForgottenPassword,
}, { initialRouteName: 'Login', headerMode: 'none', });
const RootNavigator = createSwitchNavigator({
AuthLoading: AuthLoadingScreen,
LoginNavigator,
MainNavigator
}, { initialRouteName: 'AuthLoading' });
export default class App extends Component {
render() {
return <RootNavigator />;
}
}
Any idea ?

You should consider using Switch Navigator from react-navigation for your authentication flows in your application. So once you get logged out of your app and lands on login, the application screen pops out of the memory and thus when you login back, the componentDidmount will again be triggered.

Related

React Navigation Navigate from Firebase Push Notification

I can't figure out how to navigate from push notifications from firebase fcm. I was having issues with navigation being undefined so I followed this guide, but it still doesn't change screens. Is there some basic step I am missing. I have tried to navigate to several other screens without params and none navigate, and I am not getting any errors:
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
Here is my navigation service:
this.notificationOpenedListener = firebase.notifications().onNotificationOpened((notificationOpen) => {
const { data } = notificationOpen.notification;
NavigationService.navigate("Chat", {
chatName: `${data.channelName}`,
chatId: `${data.channelId}`
});
});
And here is my main App file:
import SplashScreen from 'react-native-splash-screen';
const TopLevelNavigator = createStackNavigator({
ChatApp
},
{
headerMode: 'none',
navigationOptions: {
headerVisible: false,
}
});
const AppContainer = createAppContainer(TopLevelNavigator);
class App extends Component {
async componentDidMount() {
SplashScreen.hide();
}
render() {
return (
<Provider store={store}>
<AppContainer ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}} />
</Provider>)
}
}
export default App;
The issue was that I needed to have the other stack inside of the same navigator. Once I added that it started working
const TopLevelNavigator = createStackNavigator({
ChatApp: {
screen: ChatApp,
},
Chat: {
screen: ChatScreen,
}
});

How to pass login credentials to initial screen of DrawerMenu of navigation drawer in React Native?

I have created React-navigation Drawer V3 in my app. I am facing the problem of getting the login credentials on the initial screen that is the "Home" screen of my drawer navigation stack. Although I am passing the data on DrawerMenu.js from Login.js with the help of :
this.props.navigation.navigate('DrawerMenu', {
loginData: loginData
})
and getting the data on DrawerMenu.js with :
constructor(props) {
super(props)
this.state = {
loginData: props.navigation.state.params.loginData
}
}
But I need to get this data to Home.js which I am not able to get. The reason of getting the data to DrawerMenu.js was because there are several other screens in the stack which want the logindata and they are able to get it using screenProps used in drawer content component but as Home is the initial screen so there no way that it receives the data on the first time, only the DrawerMenu.js file receives it initially. Can someone help me out on this?
Here is my code of DrawerMenu with navigation stack.
class DrawerMenu extends Component {
constructor(props) {
super(props)
this.state = {
loginData: props.navigation.state.params.loginData
}
}
static navigationOptions =
{
header: null,
};
render() {
return (
<SafeAreaView style={styles.droidSafeArea}>
<MyApp screenProps={{ loginData: this.state.loginData}} />
</SafeAreaView>
)
}
}
class Hidden extends React.Component {
render() {
return null;
}
}
const MainScreenNavigator = createStackNavigator({
Home: { screen: Home },
Screen1: {
screen: Screen1
},
Screen2: {
screen: Screen2
},
Screen3: {
screen: Screen3
}
},
{
initialRouteName: 'Home',
});
const MyDrawerNavigator = createDrawerNavigator(
{
Main: {
screen: MainScreenNavigator,
navigationOptions: {
drawerLabel: <Hidden />
}
}
},
{
drawerWidth: 300,
contentComponent: DrawerComponent
});
const MyApp = createAppContainer(MyDrawerNavigator);
export default DrawerMenu;
I am using :
"react-native" : "0.57.5",
"react": "16.6.1",
"react-navigation": "^3.5.1"
You can use AsyncStorage to save the data
To store
_storeData = async () => {
try {
await AsyncStorage.setItem('Key Value', 'Item Value');
} catch (error) {
// Error saving data
}
};
To retrieve
_retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('Key Value');
if (value !== null) {
// We have data!!
console.log(value);
}
} catch (error) {
// Error retrieving data
}
};
Or you can use redux.

How to use AsyncStorage in createBottomTabNavigator with React Navigation?

I work with react-navigation v3 and I want to use AsyncStorage in createBottomTabNavigator for checking if user logged.
I save key to Stoage in LoginScreen:
await AsyncStorage.setItem('#MyStorage:isLogged', isLogged);
And I want to use AsyncStorage in my stack (TabStack):
const TabStack = createBottomTabNavigator(
{
Home: { screen: HomeScreen, },
// I need isLogged key from AsyncStorage here!
...(false ? {
Account: { screen: AccountScreen, }
} : {
Login: { screen: LoginScreen, }
}),
},
{
initialRouteName: 'Home',
}
);
How I can do it?
My environment:
react-native: 0.58.5
react-navigation: 3.3.2
The solution is: create a new component AppTabBar and set this in tabBarComponent property
const TabStack = createBottomTabNavigator({
Home: { screen: HomeScreen, },
Account: { screen: AccountScreen, }
},
{
initialRouteName: 'Home',
tabBarComponent: AppTabBar, // Here
});
And AppTabBar component:
export default class AppTabBar extends Component {
constructor(props) {
super(props);
this.state = {
isLogged: '0',
};
}
componentDidMount() {
this._retrieveData();
}
_retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('isLogged');
if (value !== null) {
this.setState({
isLogged: value,
});
}
} catch (error) {
// Error retrieving data
}
};
render() {
const { navigation, appState } = this.props;
const routes = navigation.state.routes;
const { isLogged } = this.state;
return (
<View style={styles.container}>
{routes.map((route, index) => {
if (isLogged === '1' && route.routeName === 'Login') {
return null;
}
if (isLogged === '0' && route.routeName === 'Account') {
return null;
}
return (
<View /> // here your tabbar component
);
})}
</View>
);
}
navigationHandler = name => {
const { navigation } = this.props;
navigation.navigate(name);
};
}
You don't need to do that, just may want to check for a valid session in the login screen.
You need to create 2 stacks, one for the auth screens and your TabStack for logged users:
const TabStack = createBottomTabNavigator({
Home: { screen: HomeScreen, },
Account: { screen: AccountScreen, }
},
{
initialRouteName: 'Home',
headerMode: 'none',
navigationOptions: {
headerVisible: false,
}
});
const stack = createStackNavigator({
Home: {screen: TabStack},
Login: { screen: LoginScreen, }
});
and then check for a valid session in LoginScreen in the method componentDidMount.
class LoginScreen extends Component {
componentDidMount(){
const session = await AsyncStorage.getItem('session');
if (session.isValid) {
this.props.navigate('home')
}
}
}
In your Loading screen, read your login state from AsyncStorage, and store it in your Redux store, ( or any sort of global data sharing mechanism of your choice ) - I'm using redux here, then read this piece of data in your Stack component like the following:
import React from "react";
import { View, Text } from "react-native";
import { connect } from "react-redux";
import { createStackNavigator } from "react-navigation";
class Stack extends React.Component {
render() {
const { isLoggedIn } = this.props.auth;
const RouteConfigs = {
Home: () => (
<View>
<Text>Home</Text>
</View>
),
Login: () => (
<View>
<Text>Login</Text>
</View>
)
};
const RouteConfigs_LoggedIn = {
Home: () => (
<View>
<Text>Home</Text>
</View>
),
Account: () => (
<View>
<Text>Account</Text>
</View>
)
};
const NavigatorConfig = { initialRouteName: "Login" };
const MyStack = createStackNavigator(
isLoggedIn ? RouteConfigs_LoggedIn : RouteConfigs,
NavigatorConfig
);
return <MyStack />;
}
}
const mapStateToProps = ({ auth }) => ({ auth });
export default connect(mapStateToProps)(Stack);

undefined is not an object (evaluating 'component.router.getStateForAction')

I'm seeing the following error when I load my react native app on my simulators. It appears to be related to my usage of react-navigation. But in googling the error, I'm unable to find what I'm missing.
It is calling out the "wrappedComponent" in this code snippet.
function WithAuth(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
ready: false,
session: null,
};
this.handleOnSignIn = this.handleOnSignIn.bind(this);
this.handleOnSignUp = this.handleOnSignUp.bind(this);
this.handleOnSignOut = this.handleOnSignOut.bind(this);
}
async componentDidMount() {
await LocalStorage.init();
let session;
try {
session = await Auth.currentSession();
} catch (err) {
console.log(err);
session = null;
}
this.setState({
session,
ready: true,
});
}
handleOnSignIn(session) {
this.setState({ session });
}
handleOnSignUp() { }
handleOnSignOut() {
Auth.signOut();
this.setState({ session: null });
}
render() {
const { ready, session } = this.state;
console.log('Rendering HOC', ready, !!session);
const {
onSignIn,
onSignUp,
doSignOut,
...otherProps
} = this.props;
return (
ready && (
<WrappedComponent
session={session}
onSignIn={onSignIn || this.handleOnSignIn}
onSignUp={onSignUp || this.handleOnSignUp}
doSignOut={doSignOut || this.handleOnSignOut}
auth={Auth}
{...otherProps}
/>
)
);
}
}
}
export default WithAuth;
which is utilizing the following app.js code:
Amplify.configure(awsmobile);
const App = createDrawerNavigator({
FirstScreen: {
screen: props => <First rootNavigator={props.navigation} screenProps={{ ...props.screenProps }} />,
navigationOptions: {
drawerLabel: ' ',
},
},
}, { initialRouteName: 'FirstScreen' });
const AppContainer = createAppContainer(props => <App screenProps={{ ...props }} />);
export default WithAuth(AppContainer);
(the export is what calls the WithAuth.)
This some of this code was pulled from an app using react-navigation 2.0 and I've updated to 3.0 and am thinking I'm missing something that is now required, however, I can't find it.
Any help is appreciated!
createAppContainer needs to directly wrap the navigator component. Updating your code, it looks like this:
Amplify.configure(awsmobile);
const App = createDrawerNavigator({
FirstScreen: {
screen: props => <First rootNavigator={props.navigation} screenProps={{ ...props.screenProps }} />,
navigationOptions: {
drawerLabel: ' ',
},
},
}, { initialRouteName: 'FirstScreen' });
const AppContainer = createAppContainer(App);
export default WithAuth(AppContainer);
Set AppContainer as your root component, like this:
const AppContainer = createAppContainer(props => WithAuth(_ => <App screenProps={{ ...props }} />));
export default AppContainer;

Not able to store logged In user data, using AsyncStorage API

I am new to react native so please bear with me.
Here is my access token's async storage, the token is one of the parameters as the response from API. and this code is in Login Screen
async getToken() {
try{
const token = await AsyncStorage.getItem('ACCESS_TOKEN');
if(token !== null) {
this.props.navigation.navigate('Profile')
}
else{
this.props.navigation.navigate('Login')
}
}
catch (error){
console.log('Something went wrong');
}
}
async storeToken(accessToken) {
try{
await AsyncStorage.setItem('ACCESS_TOKEN', accessToken);
this.getToken();
}
catch (error) {
console.log('something went wrong');
}
}
when I log-in, it is navigating me to Profile page but after refreshing the app it is going back to the login page or my navigation tree
Here is my app.js where navigation is set
const TodoNav = StackNavigator({
Login: { screen: Login },
SignUp: { screen: SignUp },
ForgotPassword: { screen: ForgotPassword },
Profile: { screen: Profile },
MenuScreen: { screen: MenuScreen },
}, {
mode: 'modal'
})
export default class App extends Component{
render() {
return (
<TodoNav />
);
}
}
AsyncStorage is not responsible to save the navigation state of your application, it will store the data as you pass to it. Refer the documentation of AsyncStorage for more details(Here)
In order to show Home screen, you need to check your async storage data at App.js
Then create Navigation stack accordingly, for eg:
If you find AccessToken:
const TodoNavAuthenticated = StackNavigator({
Profile: { screen: Profile },
MenuScreen: { screen: MenuScreen },
}, {
mode: 'modal'
})
If Accesstoken is not found means you are not logged in so:
const TodoNav = StackNavigator({
Login: { screen: Login },
SignUp: { screen: SignUp },
ForgotPassword: { screen: ForgotPassword },
Profile: { screen: Profile },
MenuScreen: { screen: MenuScreen },
}, {
mode: 'modal'
})
This needs to be checked when your using <ToDoNav /> i.e. inside your App.js render() method.