Can I loop componentWillMount until I get the user_key from API? - react-native

I am trying to use react navigation authentication flow to manage the login screen if the user is logged in or not. But now I got stuck in AsyncStorage. So while the user is not logged in I presume that componentWillMount will wait until the user will input the credentials, tap the login button, receive the user_id from API call and then try again. For me now it is calling what in the beginning which is fine but then I have to exit from app and go back to get the dashboard rendered. Any solution?
This is my code from App.js where I'm creating the routes as well. Also I am loading redux map on bottom.
export const createRootNavigator = (signedIn = false) => {
return SwitchNavigator(
{
SignedIn: {
screen: SignedIn
},
SignedOut: {
screen: SignedOut
}
},
{
initialRouteName: signedIn ? "SignedIn" : "SignedOut"
}
);
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
signedIn: false,
checkedSignIn: false
};
}
async componentWillMount() {
await isSignedIn()
.then(res => this.setState({ signedIn: res, checkedSignIn: true }))
.catch(err => alert("An error occurred"));
}
render() {
const { checkedSignIn, signedIn } = this.state;
// If we haven't checked AsyncStorage yet, don't render anything (better ways to do this)
if (!checkedSignIn) {
return null;
}
const Layout = createRootNavigator(signedIn);
return (
<SafeAreaView style={styles.safeArea}>
<View style={{flex: 1, backgroundColor: '#ffffff'}}>
<StatusBar barStyle="light-content"/>
<Layout />
<AlertContainer/>
</View>
</SafeAreaView>
)
}
};
And here is the Auth.js where I am waiting for the user_key.
export let USER_KEY = 'myKey';
export const onSignIn = async () => { await AsyncStorage.setItem(USER_KEY, 'true') };
export const onSignOut = async () => { await AsyncStorage.removeItem(USER_KEY) };
export const isSignedIn = () => {
return new Promise((resolve, reject) => {
AsyncStorage.getItem(USER_KEY)
.then(res => {
if (res !== null) {
// console.log('true')
resolve(true);
} else {
resolve(false);
// console.log('false')
}
})
.catch(err => reject(err));
});
};

A solution would be to make use of Splashscreen. You can add a splashscreen to the App. While Splashscreen is being displayed, check if user exists in Asyncstorage, if they do, navigate user to the Dashboard/Homescreen and if asynstorage responds null, navigate user to the Login page. Once Navigation is complete, you can hide the splashscreen. Checkout this package in npmjs for Splashscreen setup react-native-splash-screen

Related

How to refresh data when navigating to the previous page in React Native?

What I'm Trying To Do
When navigating back from second page to first page, I want to refresh datas.
The problem is that when I navigate back from second page, onRefresh() in First doesn't work.
First Page
export default class First extends React.Component {
constructor(props) {
super(props);
this.state = {
refreshing: false,
items: [],
};
}
fetchData = async () => {
const querySnapshot = db.items();
const items = [];
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
this.setState({ items });
}
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
this.onRefresh;
}
onRefresh() {
this.setState({ refreshing: true });
this.fetchData().then(() => {
this.setState({ refreshing: false });
});
}
render() {
return (
<Container>
<Content
refreshControl={(
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this.onRefresh.bind(this)}
/>
)}
>
</Content>
</Container>
);
}
}
Second Page
this.props.navigation.navigate('First', 'update');
I would appreciate it if you could give me any advices.
Suppose you have two pages PAGE-A & PAGE-B
create a JS-File named ForegroundBackground.js
import React from "react";
import { View } from "react-native";
const ForegroundBackground = ({ navigation, bgCallback, fgCallback }) => {
React.useEffect(() => navigation.addListener('focus', () => {
fgCallback && fgCallback()
}), []);
React.useEffect(() => navigation.addListener('blur', () => {
bgCallback && bgCallback()
}), []);
return (<View/>);
};
export default ForegroundBackground;
you can use it in render function of PAGE-A by providing navigation object to it from screen props.
<ForegroundBackground navigation = {this.props.navigation}
fgCallback = {()=>{alert("resume")}}/>
There are two solutions for the purpose.
To use store.Define a state in store and use that state in pageA and pageB.So if you change store state from pageB.It will auto reflect in entire app.
you can pass a function from pageA to pageB while navigation.The purpose of the function is to refresh state of PageA while moving back. For example:
this.props.navigation.navigate("pageB", {
resetData: () => {
this.setState({
mydata: ""
})
}
})
And while navigating from pageB you can do something like this:
this.props.navigation.state.params.resetData();
this.props.navigation.goBack();
I hope it helps. Leave a comment if you want to have more help/code/discussion etc

Handling Errors from Redux API Call as a Toast

So I'm trying to figure out the best way to display a Toast error and success function when the API call fires from redux.
My line of thinking: Create action for the API call. If successful, then I want the screen to change to the home screen. If it fails, then display the message in a Toast.
Here's what some of my actions look like:
export function getTokenAPI(username, password) {
return async function action(dispatch) {
try {
dispatch({ type: t.AUTH_GET_TOKEN });
dispatch(setLoading(true));
const { data } = await API.authGetToken(username, password);
const { success } = data;
if (success) {
const { access_token, refresh_token } = data;
dispatch(setAccessToken(access_token));
dispatch(setRefreshToken(refresh_token));
await dispatch(setLoading(false));
} else if (!success) {
const { errorMessage } = data;
throw Error(errorMessage);
}
} catch (e) {
dispatch(setError(e.message));
dispatch(setLoading(false));
}
};
}
The setError action sets the error key to true and sets the errorMessage. Here's what my screen looks like:
import React from 'react';
import { Container, View, Toast } from 'native-base';
import styles from './styles';
import { connect } from 'react-redux';
import { authActions } from '_ducks/auth';
const LoginScreen = props => {
const { getToken, navigation } = props;
const { navigate } = navigation;
const navigateToHome = () => navigate('Home');
const handleLogin = async () => {
const { error, errorMessage } = props;
await getToken('sample', 'pass123');
if (error) {
Toast.show({
text: errorMessage,
buttonText: 'kay',
});
} else {
navigateToHome();
}
};
return (
<Container>
<View style={styles.container}>
<LoginButton onPress={handleLogin} />
</View>
</Container>
);
};
const mapDispatchToProps = dispatch => ({
getToken: () => dispatch(authActions.getTokenAPI()),
});
const mapStateToProps = state => ({
isLoading: state.authReducer.isLoading,
error: state.authReducer.error,
errorMessage: state.authReducer.errorMessage,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LoginScreen);
So if there's an error, then display the toast. If it's successful, navigate to the home screen. Essentially, error will not be true quick enough to make the check within handleLogin work appropriately.
Any recommendations on the pattern or process? Should I be using a useEffect hook here?

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;

React native show a strange behavior. Can someone explain?

I'am creating a simple application with authentication. To change a state using redux with react-native-navigation (v1). For example, index.js
...
import { Navigation, } from 'react-native-navigation';
import { Provider, } from 'react-redux';
import store from './src/store';
import registerScreens from './src/screens';
registerScreens(store, Provider);
class App {
constructor () {
this.auth = false;
store.subscribe(this.onStoreUpdate.bind(this));
this.start();
}
onStoreUpdate () {
const state = store.getState();
if (this.auth != state.auth) {
this.auth = state.auth;
this.start();
}
}
start () {
switch (this.auth) {
case false:
Navigation.startTabBasedApp({
tabs: [{
screen: 'navigation.AuthScreen',
}, {
screen: 'navigation.RegisterScreen',
},],
});
break;
case true:
Navigation.startSingleScreenApp({
screen: {
screen: 'navigation.MainScreen',
},
});
break;
}
}
}
const application = new App();
Store is listening an update and change an application layout if need.
AuthScreen show a simple ActivityIndicator, when server request is perform. For example, auth.js
...
import { bindActionCreators, } from 'redux';
import { connect, } from 'react-redux';
import * as actions from './../actions';
...
class AuthScreen extends Component {
constructor (props) {
super(props);
this.state = {
loading: false,
...
};
this.handlePressEnter = this.handlePressEnter.bind(this);
}
handlePressEnter () {
...
this.loadingState(true);
jsonFetch(url, {
method: 'POST',
body: JSON.stringify({...}),
}).then((value) => {
this.loadingState();
this.props.actions.auth(true);
}).catch((errors) => {
this.loadingState();
console.log('Error while auth', errors);
});
}
...
loadingState (state = false) {
this.setState({
loading: state,
});
}
render () {
return (<View>
...
<Modal visible={this.state.loading} transparent={true} animationType="none" onRequestClose={() => {}}>
<View>
<ActivityIndicator size="large" animating={this.state.loading} />
</View>
</Modal>
</View>);
}
}
function mapStateToProps (state, ownProps) {
return {};
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps) (AuthScreen);
I'am starting application with iOS simulator and try to authenticate. It show me activity indicator, then indicator disappear, but layout does not change. And strange behavior, if I comment this.loadingState(true); and this.loadingState(); in auth.js layout changes with success.
Can someone explain to me, why layout does not change from auth to main when activity indicator using?
I think that you can use dispatch props for loading.
For example When you call this.props.actions.auth(true);
You can return loading reducers.
handlePressEnter () {
...
dispatch({ type:'loading', loading: true });
jsonFetch(url, {
method: 'POST',
body: JSON.stringify({...}),
}).then((value) => {
dispatch({ type:'loading', loading: false });
this.props.actions.auth(true);
}).catch((errors) => {
this.loadingState();
console.log('Error while auth', errors);
});
}
And than you can use
<ActivityIndicator size="large" animating={this.props.loading} />
But dont forget the reducers return

How can I change a parent state through a react navigation component?

I'm new to React Native and having trouble figuring out how to accomplish this. Currently I have an app structure something like this:
App.js -> Authentication.js -> if(state.isAuthenticated) Homepage.js, else Login.js
I'm currently changing the isAuthenticated state on a logout button on the homepage. I'm now trying to add in a drawer navigator to the app, which would get returned to the authentication page in place of the homepage. So I'm not sure how to pass the state change through the drawernavigator component to the Authentication page.
Currently my Homepage has a button that has:
onPress={() => this.props.logout()}
And the authentication page has:
export default class Authentication extends Component {
constructor(props){
super(props);
this.state = {
isAuthenticated: false,
isLoading: false
}
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
}
login() {
AsyncStorage.setItem("user", JSON.stringify({email: this.state.email, password: this.state.password}))
.then(results => {
this.setState({isAuthenticated: true});
});
}
logout() {
AsyncStorage.clear()
.then(result => {
this.setState({isAuthenticated: false});
});
}
componentDidMount(){
this.setState({isLoading: true});
AsyncStorage.getItem("user")
.then(results => {
const data = JSON.parse(results);
if (data) {
this.setState({isAuthenticated: true});
}
this.setState({isLoading: false});
});
}
render() {
if (this.state.isLoading){
return(
<Splashpage />
);
}
if (!this.state.isAuthenticated){
return (
<Login login={this.login}/>
);
}
return (
<Homepage logout={this.logout}/>
);
}
}
So I made a Navigation.js page where I'm creating a drawernavigator and going to be returning this instead of the Homepage.
export default Navigation = createDrawerNavigator({
Home: {
screen: Homepage,
},
WebView: {
screen: WebView,
},
});
But I'm not sure how to pass along the state change from the homepage, through the Navigation component to the parent Authentication page. Any help would be much appreciated.
You could pass a callback through navigate:
this.props.navigation.navigate('yourTarget',{callback:function});
In yourTraget you can access it via:
this.props.navigation.state.params.callback(isAuthenticated)
Here the documentation: https://reactnavigation.org/docs/en/params.html
I hope this is what you were looking for! Oh now I see you asked that already a while ago. Maybe you already moved on...