I'm trying to pass device token from my App.js to my Login component in react-native.
I'm trying something like this :
Here's my app.js :
const RootStack = createStackNavigator(
{
Login: {
screen: Login,
navigationOptions :{ headerLeft: null}
},
Tab: {
screen: Tab,
navigationOptions :{ headerLeft: null }
}
},
{
initialRouteName: 'LoginScreen'
}
);
const MyApp = createAppContainer(RootStack);
constructor(props) {
super(props);
this.state = {
token: ''
}
}
async componentDidMount() {
this.state.token = await firebase.messaging().getToken().then(token=> { return token;});
this.checkPermission();
this.createNotificationListeners();
}
render() {
return (
<MyApp token={this.state.token}></MyApp>
);
}
And my Login.js :
export default class LoginScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
mail:"",
password: "",
token: this.props.token
}
}
async Login(){
console.log(this.state.token)
}
}
Of course it's not working, I don't know how to pass the token via components, or via stacknavigator without using .navigate(). Besides even if I fill the const with a single string, it's not working, so what am I doind wrong ? And is it going to be different with the token ?
screenProps
You need to use screenProps. You should check the documentation to see more about it.
This is how I would use it in your case.
<MyApp screenProps={{ token: this.state.token}} />
Then you can access it in your screens using this.props.screenProps.token.
Here is a snack that I created that shows passing values through a navigator. https://snack.expo.io/#andypandy/passing-props-through-a-navigator
In your current LoginScreen you are trying to set the value of the token in the state. Bear in mind, your page may be constructed before the token is created so the initial value for the token may not exist so you may prefer to capture the value in componentDidUpdate, see the docs for more.
Alternatively you could store the token in AsyncStorage.
Firebase
When getting your token from Firebase, you are mixing promises and async/await notation. Choose one notation and stick with it.
You are setting your state incorrectly. You are mutating state with your line
this.state.token = await firebase.messaging().getToken().then(token=> { return token;});
You should not mutate state as it can get overwritten, and it can cause side effects that you don't expect. You should use this.setState({token}); to set your token value into state.
This is how I would refactor your componentDidMount
async componentDidMount() {
// await functions can throw so always wrap them in a try/catch
try {
// get the token from firebase https://rnfirebase.io/docs/v5.x.x/messaging/device-token
let token = await firebase.messaging().getToken();
if (token) {
// if we have a token then set the state and use a callback
// once the state has been the callback calls checkPermissions and createNotificationListeners
this.setState({token}, () => {
this.checkPermission();
this.createNotificationListeners();
});
}
} catch (err) {
console.log(err);
}
}
Additional reading
The following articles by Michael Chan are a great introduction into state.
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
The following article shows the differences between promises and async/await
https://medium.com/#bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8
Related
I am developing app using react native, and i have api call that retrieve json. How i can call another api in home screen immediately after it is loaded, this api i want to use for storing json data in order to use it in another screen, because i do not want the user to wait if he clicks.
The home.js that display the home screen is :
export class HomeScreen extends PureComponent {
constructor(props) {
super(props);
this.state = {
isLoading: true,
data: null,
isError: false,
}
}
componentDidMount() {
getArticles().then(data => {
this.setState({
isLoading: false,
data: data
})
}, error => {
Alert.alert("Error", "Something happend, please try again")
})
}
The function that gets articles:
export async function getArticles(){
try {
let articles = await fetch(``);
let result = await articles.json();
return result;
} catch (error) {
throw error
}
}
Can you recommend also tutorials or docs about cashing json response ?.
You can use any of these 3 third-party libraries to cache the response
[Redux] https://redux.js.org/introduction/getting-started
[React Native Async Storage] https://github.com/react-native-community/async-storage
[Context API] https://www.taniarascia.com/using-context-api-in-react/
You can call as many APIs in the componentDidMount. But if the second API is dependent on the first API then call second API in the then function
I have the following Stack Navigation:
const WelcomeStack = createStackNavigator({
UpdateProfileScreen: {
screen: UpdateProfileScreen,
},
SelectProfileScreen: {
screen: SelectProfileScreen,
},
PresentationModal: {
screen: PresentationModal,
}
}, {
initialRouteName: 'UpdateProfileScreen',
headerMode: 'none'
})
When a user is new, I show "UpdateProfileScreen" first, then I move to "SelectProfileSecreen" and then "PresentationModal".
If for some reason after "UpdateProfileScreen" user closes the app, next time they log in I will show "SelectProfileSecreen" and "PresentationModal". If they complete data, next time, they will only see the "PresentationModal"
Since I have set "UpdateProfileScreen" as initialRoute, it will always load first, even if it does not show.
So I was wondering if programatically I could change the initialRoute and/or if I do:
this.props.navigation.navigate("WelcomeStack")
I can add some logic in the same stack to handle the page to show?
I think your best option is using SwitchNavigator(RouteConfigs, SwitchNavigatorConfig) you can simply create it with createSwitchNavigator as it controls all the switching of navigators used mainly for authentication flow in most react native apps.
With that said, for your case i think its the most suitable way to achieve the desired behavior.
Please be careful if publishing an app using something along the lines of :
this.props.navigation.navigate("WelcomeStack")
as it can be a serious security vulnerability
docs: https://reactnavigation.org/docs/1.x/switch-navigator/
snack example: https://snack.expo.io/#react-navigation/auth-flow
I had a similar problem here how i resolved it:
Added SwitchNavigator like so:
let Navigation = createSwitchNavigator(
{
AuthLoading: AuthLoading,
LoginNavigator: LoginNavigator,
HomeNavTab: tabNavigator,
LoggedInChose: LoggedInChose
},
{
initialRouteName: "AuthLoading"
}
);
The AuthLoading stack is the first to load and decides where the app go next:
import React from "react";
import { connect } from "react-redux";
class AuthLoading extends React.Component {
constructor(props) {
super(props);
this.checkUserSession();
}
checkUserSession = () => {
const userData = this.props.userData;
if (userData) {
const residencesNumber = userData.nbreResidences;
if (residencesNumber == 1) {
this.props.navigation.navigate("HomeNavTab");
} else {
this.props.navigation.navigate("LoggedInChose");
}
} else {
this.props.navigation.navigate("LoginNavigator");
}
};
// Render any loading content that you like here
render() {
return (
null
);
}
}
const mapStateToProps = state => {
return {
userData: state.userData
};
};
export default connect(mapStateToProps)(AuthLoading);
Maybe you get an idea from this.
I'm using the default bottom tab navigation app from the expo example
My appnavigator.js looks like this
export default createAppContainer(createSwitchNavigator({
// You could add another route here for authentication.
// Read more at https://reactnavigation.org/docs/en/auth-flow.html
Main: MainTabNavigator,
}));
I want to check if the user is logged in, by checking the asyncstorage loginname value. In the home.js, if there is no loginname, then I want to redirect to the sigin.js page.
I suggest creating a file called initializing.js as a screen, which will be the first entry point in the app and put the logic there.
export default class Initializing extends Component {
async componentDidMount() {
try {
const token = await AsyncStorage.getItem('user_token');
const skipOnBoarding = true;
const authenticated = true;
if (token) await goToHome(skipOnBoarding, authenticated);
else await goToAuth();
SplashScreen.hide();
} catch (err) {
await goToAuth();
SplashScreen.hide();
}
}
render() {
return <View />;
}
}
I worked it out. was very easy.
First do this in the appNavigator.js
import SignIn from '../screens/SignIn'
export default createAppContainer(createSwitchNavigator({
// You could add another route here for authentication.
// Read more at https://reactnavigation.org/docs/en/auth-flow.html
Main: MainTabNavigator,
SignIn: SignIn // signIn is the login page
}));
Next, at the logic where you check for user logined do something like this.
AsyncStorage.getItem('loginname').then((value) => {
console.log(value)
this.props.navigation.navigate('SignIn')
})
the prop this.props.navigation.navigate is automatically avaiable in every stack, you dont need to pass it around to use it
I'm making authentication in an app, and I'm kind of stuck. I have 2 different navigations. One shows if the user is logged in and another one if not. Basically, a Sign in screen. It's working fine if I change the value manually upon the start. But I can't find a way to change a state when a user signs in, for example. Even though the value in auth module changes, it doesn't update in App.js So how can I update the App.js's state from Sign in screen, for example?
import React, { Component } from 'react';
import { AppRegistry, Platform, StyleSheet, Text, View } from 'react-native';
import DrawerNavigator from './components/DrawerNavigator'
import SignedOutNavigator from './components/SignedOutNavigator'
import auth from './auth'
type Props = {};
export default class App extends Component<Props> {
constructor(props) {
super(props)
this.state = {
isLoggedIn: auth.isLoggedIn
}
}
render() {
return (
(this.state.isLoggedIn) ? <DrawerNavigator /> : <SignedOutNavigator />
);
}
}
AppRegistry.registerComponent('App', () => App)
and my auth module, which is very simple
import { AsyncStorage } from 'react-native';
// try to read from a local file
let api_key
let isLoggedIn = false
function save_user_settings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
isLoggedIn = true
});
}
module.exports.save_user_settings = save_user_settings
module.exports.api_key = api_key
module.exports.isLoggedIn = isLoggedIn
First off, there are loads of ways to approach this problem. Because of this I'm going to try explain to you why what you have now isn't working.
The reason this is happening is because when you assign auth.isLoggedIn to your isLoggedIn state, you are assigning the value once, kind of as a copy. It's not a reference that is stored.
In addition to this, remember, React state is generally only updated with setState(), and that is never being called here, so your state will not update.
The way I would approach this problem without bringing in elements like Redux, which is overkill for this problem by itself, is to look into building an authentication higher order component which handles all the authentication logic and wraps your entire application. From there you can control if you should render the children, or do a redirect.
Auth Component
componentDidMount() {
this._saveUserSettings(settings);
}
_saveUserSettings(settings) {
AsyncStorage.mergeItem('user', JSON.stringify(settings), () => {
AsyncStorage.getItem('user', (err, result) => {
isLoggedIn = result.isLoggedIn
api_key = result.api_key
});
this.setState({isLoggedIn: true});
});
}
render() {
const { isLoggedIn } = this.state;
return isLoggedIn ? this.props.children : null;
}
App.js
render() {
<AuthComponent>
//the rest of authenticated app goes here
</AuthComponent>
}
Here's a really quick, incomplete example. But it should showcase to you how you may want to lay your authentication out. You'll also want to consider error handling and such, however.
I'm new with react-native and redux and I want to know how can I get the state updated after the dispatch...
Follow my code:
/LoginForm.js
function mapStateToProps(state) { return { user: state.userReducer }; }
function mapDispatchToProps(dispatch) {
return {
login: (username, password) => {
dispatch(login(username, password)); // update state loggedIn
}
}
}
const LoginForm = connect(mapStateToProps, mapDispatchToProps)(Login);
export default LoginForm;
/Login.js
---Here I've a button which calls this method loginOnPress()
loginOnPress() {
const { username, password } = this.state;
this.props.login(username, password);
console.log(this.props.user.loggedIn)
}
According my code above, I call first the method 'this.props.login(username, password);' that calls the dispatch and change the state 'loggedIn'.
And after that I try to get the state updated but without success:
console.log(this.props.user.loggedIn)
Note: When I click the second time on this button the state comes updated
Calling dispatch will update the state immediately but your components will be updated a little bit later so you can use componentWillReceiveProps to react to changes in the props, you can have a look here for a better explanation of how state change works in React
The function this.props.login(username, password) dispatches a login action on the redux-state.
Launching store.getState() will indeed immediately get you the redux-state after the update, but usually, you don't really need to do it because of the redux connect function that wraps your component.
The redux connect function updates your component with new props, so what you would usually do is "catch" these changes in one of the following functions of the react lifecycle:
class Greeting extends React.Component {
...
loginOnPress () {
const { username, password } = this.state;
this.props.login(username, password);
}
// before the new props are applied
componentWillReceiveProps (nextProps) {
console.log(nextProps.user.loggedIn)
}
// just before the update
componentWillUpdate (nextProps, nextState) {
console.log(nextProps.user.loggedIn)
}
// immediately after the update
componentDidUpdate (prevProps, prevState) {
console.log(this.props.user.loggedIn)
}
render() {
...
}
}