AWS amplify remember logged in user in React Native app - react-native

I just started exploring AWS amplify as a backend for my react native application. Being a true beginner on using the service, I want my app to remember the logged in user every time I refresh the emulator.
I know from AWS amplify documentation that I can use the Auth function currentAuthenticatedUser for this purpose, but I have no idea on how to implement a code that does this purpose.
My app looks like this:
App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import AuthTabs from './components/AuthTabs';
import NavigationTab from './components/NavigationTab';
import Amplify, { Auth } from 'aws-amplify';
import AWSConfig from './aws-exports';
Amplify.configure(AWSConfig);
export default class App extends React.Component {
state = {
isAuthenticated: false
}
authenticate(isAuthenticated) {
this.setState({ isAuthenticated })
}
render() {
if (this.state.isAuthenticated) {
console.log('Hello', Auth.user.username)
return(
<View style={styles.container}>
<Text style={styles.textStyle}>
Hello {Auth.user.username}!
</Text>
<NavigationTab
screenProps={
{authenticate: this.authenticate.bind(this)}
}
/>
</View>
)
}
return (
<View style={styles.container}>
<AuthTabs
screenProps={
{authenticate: this.authenticate.bind(this)}
}
/>
</View>
)
}
}
Any help would be much appreciated.

i have used it like this:
currentUser = () => {
Auth.currentAuthenticatedUser()
.then(user => {
console.log("USER", user);
this.props.navigation.navigate("App");
})
.catch(err => {
console.log("ERROR", err);
});
};
so, one can call it on the constructor on app refresh, and if the user is authenticated go to the main screen, but if it's not, stay in the login screen. Cheers.

I also have come up with a similar solution. But instead of the constructor, I use the life cycle method componentDidMount() to call a method that I named loadApp().
import React from 'react'
import {
StyleSheet,
View,
ActivityIndicator,
} from 'react-native'
import Auth from '#aws-amplify/auth'
export default class AuthLoadingScreen extends React.Component {
state = {
userToken: null
}
async componentDidMount () {
await this.loadApp()
}
// Get the logged in users and remember them
loadApp = async () => {
await Auth.currentAuthenticatedUser()
.then(user => {
this.setState({userToken: user.signInUserSession.accessToken.jwtToken})
})
.catch(err => console.log(err))
this.props.navigation.navigate(this.state.userToken ? 'App' : 'Auth')
}
render() {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#fff" />
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#aa73b7',
alignItems: 'center',
justifyContent: 'center',
},
})
loadApp() will try and get the user JWT Token by calling the AWS Amplify currentAuthenticatedUser() method. The obtained token is then stored in the component state.
I have used React navigation version 2 to navigate the user to either the App screen or the Auth stack screen depending on her status: logged in or not logged in.

Here is the way I handled this issue:
const currentUserInfo = await Auth.currentUserInfo()
if (currentUserInfo){
const data = await Auth.currentAuthenticatedUser()
dispatch({authTypes.FETCH_USER_DATA_SUCCESS, {payload: {user: data}}});
}

Related

Warning: React has detected a change in the order of Hooks

I have run into this error in my code, and don't really know how to solve it, can anyone help me?
I get the following error message:
ERROR Warning: React has detected a change in the order of Hooks called by ScreenA. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
import React, { useCallback, useEffect, useState } from "react";
import { View, Text, StyleSheet, Pressable } from "react-native";
import { useNavigation } from '#react-navigation/native';
import { DancingScript_400Regular } from "#expo-google-fonts/dancing-script";
import * as SplashScreen from 'expo-splash-screen';
import * as Font from 'expo-font';
export default function ScreenA({ route }) {
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
// Keep the splash screen visible while we fetch resources
await SplashScreen.preventAutoHideAsync();
// Pre-load fonts, make any API calls you need to do here
await Font.loadAsync({ DancingScript_400Regular });
// Artificially delay for two seconds to simulate a slow loading
// experience. Please remove this if you copy and paste the code!
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (e) {
console.warn(e);
} finally {
// Tell the application to render
setAppIsReady(true);
}
}
prepare();
}, []);
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
// This tells the splash screen to hide immediately! If we call this after
// `setAppIsReady`, then we may see a blank screen while the app is
// loading its initial state and rendering its first pixels. So instead,
// we hide the splash screen once we know the root view has already
// performed layout.
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;
}
const navigation = useNavigation();
const onPressHandler = () => {
// navigation.navigate('Screen_B', { itemName: 'Item from Screen A', itemID: 12 });
}
return (
<View style={styles.body} onLayout={onLayoutRootView}>
<Text style={styles.text}>
Screen A
</Text>
<Pressable
onPress={onPressHandler}
style={({ pressed }) => ({ backgroundColor: pressed ? '#ddd' : '#0f0' })}
>
<Text style={styles.text}>
Go To Screen B
</Text>
</Pressable>
<Text style={styles.text}>{route.params?.Message}</Text>
</View>
)
}
const styles = StyleSheet.create({
body: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: 40,
margin: 10,
fontFamily: 'DancingScript_400Regular'
}
})
I have read the rules of hooks: https://reactjs.org/docs/hooks-rules.html
The output is correct, but i want to fix this error before i add more additions to the app
You need to move useNavigation use before early returns.
Instead, always use Hooks at the top level of your React function, before any early returns.
The key is you need to call all the hooks in the exact same order on every component lifecycle update, which means you can't use hooks with conditional operators or loop statements such as:
if (customValue) useHook();
// or
for (let i = 0; i< customValue; i++) useHook();
// or
if (customValue) return;
useHook();
So moving const navigation = useNavigation(); before if (!appIsReady) {return null;}, should solve your problem:
export default function ScreenA({ route }) {
const [appIsReady, setAppIsReady] = useState(false);
const navigation = useNavigation();
// ...
}

Sending push notifications in Expo React Native app to multiple devices at once?

I'm still a newbie to React Native, but I am trying to integrate push notifications to my iOS & Android devices. Most tutorials I have seen with Expo use their website to push a notification using a specific token, but i do not want to do that. I want to send one notification to multiple devices at once (regardless of the OS on the device, aka iOS or Android). I think my best bet is doing it through Firebase, which is what this tutorial does. I also found the GitHub that matches the tutorial, but they use ListView, which is now deprecated. I am getting the error that I cannot use ListView in that Github's code, and when I tried to switch it to a Flatlist, I'm getting an error saying that cloneWithRows is not a function. Can someone help me with that portion of the code? Or if you have alternative resources about how to push a notification to multiple devices let me know please. Edit: I'll post the code below for convenience from the GitHub. All credits go to him. TLDR: Code doesn't work because ListView has been deprecated, and if I change it to FlatList, the cloneWithRows doesn't work. Not sure how to restructure it with a Flatlist, please help if you can.
import React from 'react';
import { StyleSheet, Text, View, StatusBar} from 'react-native';
import { Container, Content, Header, Form, Input, Item, Button, Label, Icon, List } from 'native-base'
import firebase from 'firebase';
import { Permissions, Notifications } from 'expo';
import { FlatList } from 'react-native';
import { ListView } from 'react-native';
var data = []
export default class App extends React.Component {
static navigationOptions = {
header: null
}
constructor(props) {
super(props);
this.ds = new ListView({ rowHasChanged: (r1, r2) => r1 !== r2 })
this.state = {
listViewData: data,
newContact: "",
currentUser: ""
}
}
componentDidMount() {
var currentUser
var that = this
listener = firebase.auth().onAuthStateChanged(function (user) {
if (user != null) {
currentUser = user
that.registerForPushNotificationsAsync(currentUser)
}
listener();
});
firebase.database().ref('/contacts').on('child_added', function (data) {
var newData = [...that.state.listViewData]
newData.push(data)
that.setState({ listViewData: newData })
})
}
loadSubscribers = () => {
var messages = []
//return the main promise
return firebase.database().ref('/subscribers').once('value').then(function (snapshot) {
snapshot.forEach(function (childSnapshot) {
var childKey = childSnapshot.key;
messages.push({
"to": childKey,
"sound": "default",
"body": "New Note Added"
});
});
//firebase.database then() respved a single promise that resolves
//once all the messages have been resolved
return Promise.all(messages)
}).catch(error => {
console.log(error)
})
}
registerForPushNotificationsAsync = async (currentUser) => {
const { existingStatus } = await Permissions.getAsync(Permissions.NOTIFICATIONS);
let finalStatus = existingStatus;
// only ask if permissions have not already been determined, because
// iOS won't necessarily prompt the user a second time.
if (existingStatus !== 'granted') {
// Android remote notification permissions are granted during the app
// install, so this will only ask on iOS
const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
finalStatus = status;
}
// Stop here if the user did not grant permissions
if (finalStatus !== 'granted') {
return;
}
// Get the token that uniquely identifies this device
let token = await Notifications.getExpoPushTokenAsync();
// POST the token to our backend so we can use it to send pushes from there
var updates = {}
updates['/expoToken'] = token
await firebase.database().ref('/users/' + currentUser.uid).update(updates)
//call the push notification
}
addRow(data) {
var key = firebase.database().ref('/contacts').push().key
firebase.database().ref('/contacts').child(key).set({ name: data })
}
async deleteRow(secId, rowId, rowMap, data) {
await firebase.database().ref('contacts/' + data.key).set(null)
rowMap[`${secId}${rowId}`].props.closeRow();
var newData = [...this.state.listViewData];
newData.splice(rowId, 1)
this.setState({ listViewData: newData });
}
render() {
return (
<Container style={styles.container} >
<Header style={{ marginTop: StatusBar.currentHeight }}>
<Content>
<Item>
<Input
onChangeText={(newContact) => this.setState({ newContact })}
placeholder="Add Note"
/>
<Button onPress={() => this.addRow(this.state.newContact)}>
<Icon name="add" />
</Button>
</Item>
</Content>
</Header>
<Content>
<List
enableEmptySections
dataSource={this.ds.cloneWithRows(this.state.listViewData)}
renderRow={data =>
<ListItem>
<Text> {data.val().name}</Text>
</ListItem>
}
renderLeftHiddenRow={data =>
<Button full onPress={() => this.addRow(data)} >
<Icon name="information-circle" />
</Button>
}
renderRightHiddenRow={(data, secId, rowId, rowMap) =>
<Button full danger onPress={() => this.deleteRow(secId, rowId, rowMap, data)}>
<Icon name="trash" />
</Button>
}
leftOpenValue={-75}
rightOpenValue={-75}
/>
</Content>
</Container>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});

Save react-native-check-box status after reload

I am building a React Native Iphone App.I have a checkbox "Remember me" in Login page, which I want to set to remember the username and password in order to login.I want to save the status of checkbox even after reload(Once it is ticked it should persist till it is ticked-off by the user).Below is my code.
index.js :
import React, { Component } from 'react';
import { View,KeyboardAvoidingView, Text, StyleSheet, Dimensions} from 'react-
native';
import CheckBox from 'react-native-check-box';
import AsyncStorage from '#react-native-community/async-storage';
export default class index extends Component{
constructor() {
super();
this.state = {
status: false
};
toggleStatus = async() =>{
this.setState({
status: !this.state.status
});
AsyncStorage.setItem("myCheckbox",JSON.stringify(this.state.status));
}
}
componentWillMount(){
AsyncStorage.getItem('myCheckbox').then((value) => {
this.setState({
status: (value === 'true')
});
});
}
render() {
return (
<KeyboardAvoidingView
style={{ flex: 1, backgroundColor: 'white', justifyContent: 'flex-end'}}
behavior="padding"
keyboardVerticalOffset={50}
enabled>
<Text>{typeof this.state.status +' : '+ this.state.status}</Text>
<CheckBox
style={{flex: 1,paddingLeft:100,paddingTop:20}}
onClick={()=>{
this.setState({
isChecked:!this.state.isChecked
})
toggleStatus(this)
}}
isChecked={this.state.isChecked}
rightText={"Remember me"}
/>
</KeyboardAvoidingView>
);
}
}
index.navigationOptions = {
headerTitle: ''
};
const styles = StyleSheet.create({
});
I could save the status but not set it after reload.I have tried some techniques using the stackoverflow logics, but dint give me proper result.Can anyone help me to set the checkbox.Thanks in advance.
I think you are making a mistake in your toggle method. async doesn't work here (Also we need to use await with async) you should write your code like this. setState take time to save the state so you need to use its callback function which called after the state saved.
I am showing 2 ways here but I prefer the first one.
toggleStatus =() =>{
this.setState({
status: !this.state.status
}, () => AsyncStorage.setItem("myCheckbox",JSON.stringify(this.state.status)));
}
OR
You can do like
toggleStatus = () =>{
AsyncStorage.setItem("myCheckbox",JSON.stringify(!this.state.status));
this.setState({
status: !this.state.status
});
}

Unstated store based React Navigation causing warning

I'm using react-navigation and Unstated in my react native project.
I have a situation where I would like use:
this.props.navigation.navigate("App")
after successfully signing in.
Problem is I don't want it done directly from a function assigned to a submit button. I want to navigate based upon a global Unstated store.
However, it means that I would need to use a conditional INSIDE of the Subscribe wrapper. That is what leads to the dreaded Warning: Cannot update during an existing state transition (such as within 'render').
render() {
const { username, password } = this.state;
return (
<Subscribe to={[MainStore]}>
{({ auth: { state, testLogin } }) => {
if (state.isAuthenticated) {
this.props.navigation.navigate("App");
return null;
}
console.log("rendering AuthScreen");
return (
<View style={styles.container}>
<TextInput
label="Username"
onChangeText={this.setUsername}
value={username}
style={styles.input}
/>
<TextInput
label="Password"
onChangeText={this.setPassword}
value={password}
style={styles.input}
/>
{state.error && (
<Text style={styles.error}>{state.error.message}</Text>
)}
<Button
onPress={() => testLogin({ username, password })}
color="#000"
style={styles.button}
>
Sign in!
</Button>
</View>
);
}}
</Subscribe>
);
It works. But what's the correct way to do it?
I don't have access to MainStore outside of Subscribe and therefore outside of render.
I'm not sure about the react-navigation patterns but you could use a wrapper around this component which subscribes to 'MainStore' and pass it down to this component as a prop. That way you'll have access to 'MainStore' outside the render method.
I have since found a better solution.
I created an HOC that I call now on any Component, functional or not, that requires access to the store. That give me access to the store's state and functions all in props. This means, I am free to use the component as it was intended, hooks and all.
Here's what it looks like:
WithUnstated.js
import React, { PureComponent } from "react";
import { Subscribe } from "unstated";
import MainStore from "../store/Main";
const withUnstated = (
WrappedComponent,
Stores = [MainStore],
navigationOptions
) =>
class extends PureComponent {
static navigationOptions = navigationOptions;
render() {
return (
<Subscribe to={Stores}>
{(...stores) => {
const allStores = stores.reduce(
// { ...v } to force the WrappedComponent to rerender
(acc, v) => ({ ...acc, [v.displayName]: { ...v } }),
{}
);
return <WrappedComponent {...allStores} {...this.props} />;
}}
</Subscribe>
);
}
};
export default withUnstated;
Used like so in this Header example:
import React from "react";
import { Text, View } from "react-native";
import styles from "./styles";
import { states } from "../../services/data";
import withUnstated from "../../components/WithUnstated";
import MainStore from "../../store/Main";
const Header = ({
MainStore: {
state: { vehicle }
}
}) => (
<View style={styles.plateInfo}>
<Text style={styles.plateTop}>{vehicle.plate}</Text>
<Text style={styles.plateBottom}>{states[vehicle.state]}</Text>
</View>
);
export default withUnstated(Header, [MainStore]);
So now you don't need to create a million wrapper components for all the times you need your store available outside of your render function.
As, as an added goodie, the HOC accepts an array of stores making it completely plug and play. AND - it works with your navigationOptions!
Just remember to add displayName to your stores (ES-Lint prompts you to anyway).
This is what a simple store looks like:
import { Container } from "unstated";
class NotificationStore extends Container {
state = {
notifications: [],
showNotifications: false
};
displayName = "NotificationStore";
setState = payload => {
console.log("notification store payload: ", payload);
super.setState(payload);
};
setStateProps = payload => this.setState(payload);
}
export default NotificationStore;

can react-native-root-siblings work with react-redux

in a handleClick function, update the rootSiblings like this,
handleClick() { this.progressBar.update( <ProgressBar /> ); }
and in ProgressBar component,
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { View } from 'react-native';
const getFinishedWidth = progress => ({ width: progress * totalWidth });
const getUnfinishedWidth = progress => ({ width: (1 - progress) * totalWidth });
function CustomerReassignProgressBar(props) {
const { progress } = props;
return (
<View style={styles.bar}>
<View style={getFinishedWidth(progress)} />
<View style={getUnfinishedWidth(progress)} />
</View> );
}
CustomerReassignProgressBar.propTypes = { progress: PropTypes.number, };
const mapStateToProps = state => ({ progress: state.batchReassignProgress, });
export default connect(mapStateToProps)(ProgressBar);
then, when calling handleClick(), the app crushed, the error is, 'Could not find "store" in either the context or props of "Connect(ProgressBar)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(ProgressBar)".'
if I don't use connect in component, it works well. So, I guess, maybe rootSiblings can not work with react-redux. But does anyone knows this problem?
Upgrade to react-native-root-siblings#4.x
Then
import { setSiblingWrapper } from 'react-native-root-siblings';
import { Provider } from 'react-redux';
const store = xxx;// get your redux store here
// call this before using any root-siblings related code
setSiblingWrapper(sibling => (
<Provider store={store}>{sibling}</Provider>
));