AWS Amplify: onStatusChange then render main page - javascript

I am trying to render the main entry point of my application when an auth status change occurs but when I do try to go to the main entry point of my application I get a blank screen. I'm assuming I can't go to a main page if it isn't being called within the render function itself? If so, how do I render the main Page of my app when I established signIn?
index.js
class App extends Component {
/*
constructor(props) {
super(props);
this.state = {
authState: null,
authData: null
}
}
*/
handleAuthStateChange(state) {
alert(state)
if (state === 'signedIn') {
alert('here');
return ( // go To Entry point of app
<ApolloProvider store={store} client={client}>
<AppWithNavigationState/>
</ApolloProvider>
);
}
}
render() {
//const { authState, authData } = this.state;
// const signedIn = (authState === 'signedIn');
return (
<Authenticator hideDefault={true} onStateChange={this.handleAuthStateChange}>
<Login/>
</Authenticator>
);
}
}
export default App = codePush(App);
Login.js
export default class Login extends Component {
render() {
const { authState, onStateChange } = this.props;
if (!['signIn', 'signedOut', 'signedUp'].includes(authState)) {
alert('login')
return null;
}
return (<View style={styles.container}>
<View style={styles.backgroundContainer}>
<Image source={images.icons.LoginImage} style={styles.backgroundImage} />
</View>
<View style={styles.overlay}>
<Button iconLeft light rounded style={styles.facebookLoginButton}>
<Icon style={styles.facebookIcon} name='logo-facebook' />
<Text style={styles.facebookButtonText}>Login with Facebook</Text>
</Button>
<Button rounded bordered light style={styles.loginLaterButton}
onPress={() => onStateChange('signedIn')}>
<Text style={styles.buttonText}>Sign In Test</Text>
</Button>
</View>
</View>
);
}
}

If you resolved it I hope it will help someone else.
I think the following is a better option than using 'onAuthStateChange':
from Amplify dox :
import { Auth } from 'aws-amplify';
Auth.currentAuthenticatedUser({
bypassCache: false // Optional, By default is false. If set to true, this call will send a request to Cognito to get the latest user data
}).then(user => console.log(user))
.catch(err => console.log(err));
You can add your logic within '.then(user => ...' to add route to your protected pages. Also you can redirect to Login page from '.catch(err => '.
If you include above code within a function and call it from 'componentWillReceiveProps' it should be called every time auth status changes.

This is really about rendering and state (and not anything to do with AWS Amplify). First, set up state in your constructor:
constructor(props) {
super(props);
this.state = { authState: '' };
}
Then, your onAuthStateChange() becomes:
onAuthStateChange(newState) {
if (newState === 'signedIn') {
this.setState({ authState: newState });
}
}
Finally, in your render() method, you adjust your rendering so that it does "the right thing" based on your auth state.
render() {
if (this.state.authState === 'signedIn') {
return (<ApolloProvider ...><MyApp/></ApolloProvider>);
} else {
return (<Authenticator .../>);
}
}
You can abstract this away with a HOC as well (the same way the withAuthenticator() HOC from AWS Amplify does it). Basically, the Authenticator gets displayed initially. Once the signedIn state is received, the internal component state is updated and that causes a re-render of the component with the rest of your app.

Related

Tips for displaying components based on global state

I'm build game interface that has the following user flow:
user lands on one of the games URL eg. www.name.com/game1, first gets intro screen, than game screen and finally fail or success screen.
I'm trying to figure out the most optimal way to do this. Bellow is the code that works just fine but I'm looking for more elegant and scale-able solution. Any idea?
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
// Import views and components
import Step1 from "../Intro/Step1";
import StatusBar from "../../components/StatusBar/StatusBar";
import Game1 from "./Games/Game1/Game1";
import Game2 from "./Games/Game2/Game2";
import Intro from "./Intro/Intro";
import Password from "./Password/Password";
import Success from "./Success/Success";
import Fail from "./Fail/Fail";
import FailBeginOnStart from "./Fail/FailBeginOnStart";
// Data
function Game() {
const data = {
game1: {
desc: "some description for game 1",
},
game2: {
desc: "some description for game 2",
},
};
// Get global states from redux toolkit
const showIntro = useSelector((state) => state.game.showIntro);
const showSuccess = useSelector((state) => state.game.showSuccess);
const showFail = useSelector((state) => state.game.showFail);
const showPassword = useSelector((state) => state.game.showPassword);
const completedGame = useSelector((state) => state.game.completedGame);
const selectedLanguage = useSelector((state) => state.game.selectedLanguage);
// Get current param from URL (example /game1)
const { game } = useParams();
// Strip slash to get matching game ID (example game1)
const gameId = game.replace(/-/g, "");
const GameScreen = () => {
// show intro screen
if (showIntro === true) {
return (
<>
<StatusBar />
<Intro path={game} id={gameId} data={data[gameId]} />
</>
);
}
// show success screen
if (showSuccess === true) {
return (
<>
<StatusBar />
<Success data={data[gameId]} />
</>
);
}
// show fail screen
if (showFail === true) {
return (
<>
<StatusBar />
<Fail data={data[gameId]} />
</>
);
}
// Show actual game
switch (true) {
case game === "game1":
return <Game1 data={data[gameId]} />;
case game === "game2":
return <Game2 data={data[gameId]} />;
default:
return <Step1 />;
}
};
return <GameScreen />;
}
export default Game;
I'd suggest React Router and changing to class based components for your use case. You'd do a BaseGame class as a template for the concrete games. (if you're using typescript you can make it an abstract class.).
These examples are without further information on the actual flow of your page so you might need to adjust it.
class BaseGame extends React.Component {
// creating dummy members that get overwritten in concrete game classes
desc = "";
id = 0;
data = {}
constructor(){
this.state={
intro: true,
success: false,
fail: false
}
}
/** just dummy functions as we don't have access to
/* abstract methods in regular javascript.
/*
*/
statusBar(){ return <div>Statusbar</div>}
gameScreen(){ return <div>the Game Screen</div>}
render(){
return (
{this.statusBar()}
{if(this.state.intro) <Intro data={this.data} onStart={() => this.setState({intro: false})}/>}
{if(this.state.success) <Success data={this.data}/>}
{if(this.state.fail) <Faildata={this.data}/>}
{if(!this.state.intro && !this.state.fail && !this.state.success) this.gameScreen()}
)
}
}
class Game1 extends BaseGame {
id = 1;
data = {GameData}
// actual implementation of the screens
statusBar(){ return <div>Game1 StatusBar</div>}
gameScreen(){ return (
<div>
<h1>The UI of Game 1</h1>
Make sure to setState success or failure at the end of the game loop
</div>
)}
}
and in your app function
const App = () => (
<Router>
<Switch>
<Route exact path="/" component={Step1} />
<Route path="/game1" component={Game1} />
<Route path="/game2" component={Game2} />
...
</Switch>
</Router>
)
just a quick idea. You propably want to add a centralized Store like Redux depending where you wanna manage the State. But the different Game Classes can very well manage it on their own when they don't need a shared state. Also depending what the statusBar does it can be outside of the game and just in your app function
...
<StatusBar />
<Router>
... etc.
</Router>
in your app function.

react-native : update state then check with addListener

I am trying to update state then check the state with the 'addListener' in the 'componentDidMount'
When I update my state, I can see that my state is successfully updated but the addListener won't update.
I think I should see the console.log('state is the same') when I update my state but noting is happening.
Any Ideas?
This is my code:
export default class Update extends React.Component {
constructor(props) {
super(props)
this.state = {
number:1,
newNumber:undefined
}
console.log(this.state.number, 'number')
}
componentDidMount() {
this.props.navigation.addListener('willFocus', () => {
//when screen is loaded I get this console.
if(this.state.number != this.state.newNumber){
console.log('state is not the same')
}
//when I update state I should see this console log but nothing is happening.
if(this.state.number == this.state.newNumber){
console.log('state is the same')
}
})
}
render() {
return (
<View style={{flex:1, justifyContent:'center', alignItems:'center'}}>
<Button title='update' onPress={()=> this.setState({newNumber:1}, ()=> console.log(this.state.newNumber, 'newNumber'))}/>
</View>
);
}
}
Basically what you are trying to achieve is to check that onClick of button , there is a newState which is set , and you want to check with existing one , did it update correctly or not.
So you are using this.props.navigation.addListener('willFocus') which gets called when the screen is focused while coming from other screens. but in your case you are in the same screen and you update the state by calling the button, so it wont get called.
Rather react has its own lifecylce method called componentDidUpdate , and if you check the condition there for any state updates you will achieve it.
Please find below code and also expo link,
export default class Update extends React.Component {
constructor(props) {
super(props)
this.state = {
number:1,
newNumber:undefined
}
console.log(this.state.number, 'number')
}
componentDidUpdate(){
if(this.state.number != this.state.newNumber){
alert('state is not the same')
}
//when I update state I should see this console log but nothing is happening.
if(this.state.number == this.state.newNumber){
alert('state is the same')
}
}
render() {
return (
<View style={{flex:1, justifyContent:'center', alignItems:'center'}}>
<Button title='update' onPress={()=> this.setState({newNumber:1}, ()=> console.log(this.state.newNumber, 'newNumber'))}/>
</View>
);
}
}
expo link expo
hopeit helps, feel free for doubts
the reason why your code wont work is because you want to listen to state changes but your listener only listen to navigation changes which only triggered when the screen will focus.
to detect the changes to your state in class component, you can always use the lifecycle of react which is componentDidUpdate
what you can do perhaps something like this.
export default class Update extends React.Component {
constructor(props) {
super(props)
this.state = {
number:1,
newNumber: null
}
console.log(this.state.number, 'number')
}
componentDidMount() {
this.check()
}
componentDidUpdate(prevState) {
if(prevState.newNumber !== this.state.newNumber) {
this.check()
}
}
check = () => {
if(this.state.number != this.state.newNumber){
console.log('state is not the same')
} else {
console.log('state is the same')
}
}
render() {
return (
<View style={{flex:1, justifyContent:'center', alignItems:'center'}}>
<Button title='update' onPress={()=> this.setState({newNumber:1}, ()=> console.log(this.state.newNumber, 'newNumber'))}/>
</View>
);
}
}

RN - Change parents' screenProps

I'm working on a React Native menu with a StackNavigator. If the user press a ListItem an id should be passed to all other Tabs in this menu, so the data can get fetched from an API. I tried to use screenProps to pass the data. Unfortunately I wasn't able to reset the value, when pressing a ListItem.
export default class Index extends Component {
constructor(props){
super(props);
}
render() {
return (
<OrderScreen
screenProps={ { Number: 123 } }
/>
);
}
}
In the child components I can access the prop but not reassign it:
export default class ListThumbnailExample extends Component
{
constructor(props)
{
super(props);
const{screenProps} = this.props;
this.state = { epNummer: screenProps.Number };
}
render()
{
return (
<Content>
<List>
{
this.state.orders.map(data => (
<ListItem key = {data.Number}
onPress = {() =>
{
this.props.screenProps.Number = data.Number;
this.props.navigation.navigate('Orders')
}
}
<Text>{ data.name }</Text>
</ListItem >
))
}
</List>
</Content >
);
}
}
Thank you!
In React and React-native props are immutable by design :
https://reactjs.org/docs/components-and-props.html#props-are-read-only
In your case if you want to pass screen-specific data you may wanna try passing them in the params of the navigation.navigate() function like this :
this.props.navigation.navigate('Orders',data.Number)
you can then access them in "Orders" screen from : props.navigation.state.params
More information here : https://reactnavigation.org/docs/params.html

Passing props into state in React Native?

I have a button on the homescreen which toggles the text in the AlertBar.
So when I press a Button, the text in AlertBar should change according to the state isParked. Currently when I press the button, nothing happens... and I'm unsure why.
Here's my homescreen:
class Home extends Component {
constructor(props) {
super(props);
this.state = {
isParked: false
};
}
pressPark = () => this.setState({isParked:true})
render() {
console.ignoredYellowBox = ['Remote debugger'];
return (
<View>
<View>
<AlertBar isParked={this.state.isParked}/>
</View>
<View style={styles.parkButton}>
<Button title='PARK' onPress={this.pressPark} color='green'/>
</View>
</View>
);
}
}
Here's my AlertBar.js:
class AlertBar extends Component {
state = {
region: 'Singapore',
isParked: this.props.isParked,
alertText: null
}
... some unrelated code ...
componentDidMount() {
if (this.state.isParked === false) {
this.setState({alertText: "You're parking at"})} else if (this.state.isParked === true) {
this.setState({alertText: "You're parked at"})}
alert(this.state.alertText)
}
componentWillUnmount() {
// some unrelated code
}
render() {
... some unrelated code...
return(
<View style={styles.container}>
<Text style={styles.welcomeText}>
{this.state.alertText}
</Text>
<Text style={styles.locationText}>
{this.state.region}
</Text>
</View>
)
}
}
Am I doing this wrong? I can't tell what's wrong.... Please help! Thanks!
Use
if (this.props.isParked === false)
Instead of
if (this.state.isParked === false)
(and dont transfer props to state directly, this make no sense anyway :))
At this point, your AlertBar component is not handling any prop changes.
What you'll need to do, is map your props to the state whenever an update is received.
Add this line of code in your AlertBar.js and it will map isParked to state whenever it receives an update.
componentWillReceiveProps(props) {
this.setState({ isParked: props.isParked });
}

How to filter data in ListView React-native?

I am trying to filter my array object list and then trying to display in the ListView with new DataSource. However, the list is not getting filtered. I know that my filter function works correctly. ( I checked it in the console.log )
I am using Redux to map my state to prop. And then trying to filter the prop. Is this the wrong way?
Here is my code:
/*global fetch:false*/
import _ from 'lodash';
import React, { Component } from 'react';
import { ListView, Text as NText } from 'react-native';
import { connect } from 'react-redux';
import { Actions } from 'react-native-router-flux';
import {
Container, Header, Item,
Icon, Input, ListItem, Text,
Left, Right, Body, Button
} from 'native-base';
import Spinner from '../common/Spinner';
import HealthImage from '../misc/HealthImage';
import { assetsFetch } from '../../actions';
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
class AssetsList extends Component {
componentWillMount() {
this.props.assetsFetch();
// Implementing the datasource for the list View
this.createDataSource(this.props.assets);
}
componentWillReceiveProps(nextProps) {
// Next props is the next set of props that this component will be rendered with.
// this.props is still equal to the old set of props.
this.createDataSource(nextProps.assets);
}
onSearchChange(text) {
const filteredAssets = this.props.assets.filter(
(asset) => {
return asset.name.indexOf(text) !== -1;
}
);
this.dataSource = ds.cloneWithRows(_.values(filteredAssets));
}
createDataSource(assets) {
this.dataSource = ds.cloneWithRows(assets);
}
renderRow(asset) {
return (
<ListItem thumbnail>
<Left>
<HealthImage health={asset.health} />
</Left>
<Body>
<Text>{asset.name}</Text>
<NText style={styles.nText}>
Location: {asset.location} |
Serial: {asset.serial_number}
</NText>
<NText>
Total Samples: {asset.total_samples}
</NText>
</Body>
<Right>
<Button transparent onPress={() => Actions.assetShow()}>
<Text>View</Text>
</Button>
</Right>
</ListItem>
);
}
render() {
return (
<Input
placeholder="Search"
onChangeText={this.onSearchChange.bind(this)}
/>
<ListView
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
);
}
}
const mapStateToProps = state => {
return {
assets: _.values(state.assets.asset),
spinner: state.assets.asset_spinner
};
};
export default connect(mapStateToProps, { assetsFetch })(AssetsList);
What am I doing wrong here?
It's a little hard to follow what's going on here. I would simplify it to be like so:
class AssetsList extends Component {
state = {};
componentDidMount() {
return this.props.assetsFetch();
}
onSearchChange(text) {
this.setState({
searchTerm: text
});
}
renderRow(asset) {
return (
<ListItem thumbnail>
<Left>
<HealthImage health={asset.health} />
</Left>
<Body>
<Text>{asset.name}</Text>
<NText style={styles.nText}>
Location: {asset.location} |
Serial: {asset.serial_number}
</NText>
<NText>
Total Samples: {asset.total_samples}
</NText>
</Body>
<Right>
<Button transparent onPress={() => Actions.assetShow()}>
<Text>View</Text>
</Button>
</Right>
</ListItem>
);
}
getFilteredAssets() {
}
render() {
const filteredAssets = this.state.searchTerm
? this.props.assets.filter(asset => {
return asset.name.indexOf(this.state.searchTerm) > -1;
})
: this.props.assets;
const dataSource = ds.cloneWithRows(filteredAssets);
return (
<Input
placeholder="Search"
onChangeText={this.onSearchChange.bind(this)}
/>
<ListView
enableEmptySections
dataSource={dataSource}
renderRow={this.renderRow}
/>
);
}
}
const mapStateToProps = state => {
return {
assets: _.values(state.assets.asset),
spinner: state.assets.asset_spinner
};
};
export default connect(mapStateToProps, { assetsFetch })(AssetsList);
A few points:
Your component is stateful. There is one piece of state that belongs only to the component: the search term. Keep that in component state.
Don't change the data source in life cycle functions. Do it the latest point you know it's needed: in render.
I'm guessing that there's something async in assetFetch, so you probably should return it in componentDidMount.
I changed from componentWillMount to componentDidMount. It's recommended to put async fetching componentDidMount. This can matter if you ever do server side rendering.
Skip filtering if there is no search term. This would only matter if the list is very large.
One thing I have a little concern with is the pattern of fetching inside a component, putting it in global state, and then relying on that component to react to the global state change. Thus changing global state becomes a side effect of simply viewing something. I assume you are doing it because assets is used elsewhere, and this is a convenient point to freshen them from the server so that they will show up in other components that do not fetch them. This pattern can result in hard-to-find bugs.
You need to do setState to trigger render. Here's how I would do it -
constructor(props) {
super(props);
this.ds = new ListView.DataSource({ rowHasChanged: (r1,r2) => r1 !== r2 });
this.state = {
assets: []
};
}
componentWillMount() {
this.props.assetsFetch();
this.setState({
assets: this.props.assets
});
}
componentWillReceiveProps(nextProps) {
this.setState({
assets: nextProps.assets
});
}
onSearchChange(text) {
const filteredAssets = this.props.assets.filter(asset => asset.name.indexOf(text) !== -1);
this.setState({
assets: _.values(filteredAssets)
});
}
render() {
...
<ListView
dataSource={this.ds.cloneWithRows(this.state.assets)}
.....
/>
}

Categories