Im building a react native app and I faced a problem on componentDidUpdate.
When the app loads the componentDidMount call an api to check if the user are logged (using firebaseService.auth().onAuthStateChanged ), if it is, the app is redirected to main screen, otherwise to login screen. But the component just redirect to one of this screen when I click somewhere. Can someone help me?
Thanks
Follow my code:
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, Button, Image, FlatList,ActivityIndicator} from 'react-native';
import logo from '../../asserts/logo.png'
import { TouchableOpacity, TextInput } from 'react-native-gesture-handler';
import ListCard from '../../components/User/ListCard';
import { connect } from 'react-redux';
import axios from 'axios';
import { restoreSession} from '../../store/actions/Section/actions';
class Load extends Component {
componentDidMount(){
this.props.restore();
const { user, logged, error, loading } = this.props;
console.log("restore");
if(user && logged) this.props.navigation.navigate('User');
}
componentDidUpdate(prevProps) {
const { user, logged, error,loading } = this.props;
console.log("aqui");
if (!loading && !prevProps.error && error) Alert.alert('error', error);
if (!loading && logged) this.props.navigation.navigate('User');
}
constructor() {
super();
this.state = {
animating: false,
align: 'flex-start',
alignsecond: false,
};
setTimeout(
() =>
this.setState({ align: 'flex-start' }, function() {
this.setState({
alignsecond: true,
});
}),
100
);
}
render() {
return (
<View
style={{
flex: 1,
alignItems: 'center',
//flexDirection: 'row',
justifyContent: this.state.align,
marginTop : 150,
}}>
<Image
source={logo}
style={{ width: 200, height: 200 }}
/>
{!this.state.alignsecond ? null : (
<View style={{ margin: 10, justifyContent:'center',alignItems:'center' }}>
<Text
style={{ color: '#6F1121', fontSize: 30, fontWeight: 'bold' ,justifyContent:'center'}}>
HomeShare
</Text>
<Text
style={{ fontSize: 15,justifyContent:'center' }}>
Find a place to Share !
</Text>
<ActivityIndicator style={{ marginTop:20 }} size="large" color="gray" />
</View>
)}
</View>
);
}
}
const styles = StyleSheet.create({
containerCard:{
flex:1,
flexDirection: 'column',
paddingTop:10 ,
paddingLeft:20,
paddingRight:20,
backgroundColor: 'rgba(220, 222, 211, 0.25)',
// marginTop: (Platform.OS === 'ios') ? 44 : StatusBar.currentHeight,
},
container:{
flex:1,
backgroundColor: 'rgba(220, 222, 211, 0.25)',
}
});
const mapStateToProps = ({ section: { restoring, loading, user, error, logged } }) => ({
restoring: restoring,
loading: loading,
user: user,
error: error,
logged: logged
});
const mapDispatchToProps = {
//login:loginUser,
restore:restoreSession
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Load);
My Action:
export const restoreSession = () => dispatch => {
dispatch(sessionLoading());
firebaseService.auth().onAuthStateChanged(async function(user) {
if (user) {
//console.log(user);
firebaseService.auth().currentUser.getIdToken(true).then(function(idToken) {
dispatch(sessionSuccess(idToken))
}).catch(function(error) {
dispatch(sessionError(e));
});
//dispatch(sessionSuccess(user));
} else {
dispatch(sessionLogout);
}
})
};
Personally, I would lose the logic for the componentDidMount and componentDidUpdate and just put the onAuthStateChange here in the componentDidMount so you can execute the navigate to the page.
Anyway, your problem is that the componentDidUpdate is executed when the page refreshes and that won't happen until you interact with it, and that is why it only analyzes the logged variable after you touch the screen. If you are thinking that the componentDidUpdate will execute every time you receive a new prop, that is wrong.
You should check this https://es.reactjs.org/docs/react-component.html#static-getderivedstatefromprops
Related
I'm learning react native through a book and I was following the guide step by step. As I was coding and seeing the changes in the app I came across this error and I don't know what it means. I reviewed the code and compared with the code of the book and it's the same. I also searched the internet for some answers and couldn't find any. I'd be really glad if someone could tell me what I'm doing wrong here.
Here's the error I'm getting on the app:
The error in the windows terminal:
TypeError: undefined is not an object (evaluating '_this.submitTodo.bind')
This error is located at:
in App (at TodoApp/index.js:9)
in TodoApp (at renderApplication.js:50)
in RCTView (at View.js:32)
in View (at AppContainer.js:92)
in RCTView (at View.js:32)
in View (at AppContainer.js:119)
in AppContainer (at renderApplication.js:43)
in TodoApp(RootComponent) (at renderApplication.js:60)
Here's the App.js code:
import React, { Component } from 'react'
import { View, ScrollView, StyleSheet } from 'react-native'
import Heading from './Heading'
import Input from './Input'
import Button from './Button'
let todoIndex = 0
class App extends Component {
constructor() {
super()
this.state = {
inputValue: '',
todos: [],
type: 'All'
}
this.submitTodo = this.submitTodo.bind(this)
}
inputChange(inputValue) {
console.log(' Input Value: ' , inputValue)
this.setState({ inputValue })
}
sumbitTodo () {
if (this.state.inputValue.match(/^\s*$/)) {
return
}
const todo = {
title: this.state.inputValue,
todoIndex,
complete: false
}
todoIndex++
const todos = [...this.state.todos, todo]
this.setState({ todos, inputValue: '' }, () => {
console.log('State: ', this.state)
})
}
render () {
const { inputValue } = this.state
return (
<View style={styles.container}>
<ScrollView keyboardShouldPersistTaps='always' style={styles.content}>
<Heading />
<Input
inputValue={inputValue}
inputChange={ (text) => this.inputChange(text) } />
<Button submitTodo={this.submitTodo} />
</ScrollView>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5'
},
content: {
flex: 1,
paddingTop: 60
}
})
export default App
Here's the Button.js code which might help:
import React from 'react'
import { View, Text, StyleSheet, TouchableHighlight } from 'react-native'
const Button = ({ submitTodo }) => (
<View style={styles.buttonContainer}>
<TouchableHighlight
underlayColor='#efefef'
style={styles.button}
onPress={submitTodo}>
<Text style={styles.submit}>
Submit
</Text>
</TouchableHighlight>
</View>
)
const styles = StyleSheet.create({
buttonContainer: {
alignItems: 'flex-end'
},
button: {
height: 50,
paddingLeft: 20,
paddingRight: 20,
backgroundColor: '#ffffff',
width: 200,
marginRight: 20,
marginTop: 15,
borderWidth: 1,
borderColor: 'rgba(0,0,0,.1)',
justifyContent: 'center',
alignItems: 'center'
},
submit: {
color: '#666666',
fontWeight: '600'
}
})
export default Button
Here's the Input.js code which might help as well.
import React from 'react'
import { View, TextInput, StyleSheet } from 'react-native'
const Input = ({ inputValue, inputChange }) => (
<View style={styles.inputContainer}>
<TextInput
value={inputValue}
style={styles.input}
placeholder='What needs to be done?'
placeholderTextColor='#CACACA'
selectionColor='#666666'
onChangeText={inputChange} />
</View>
)
const styles = StyleSheet.create({
inputContainer: {
marginLeft: 20,
marginRight: 20,
shadowOpacity: 0.2,
shadowRadius: 3,
shadowColor: '#000000',
shadowOffset: { width: 2, height: 2 }
},
input: {
height: 60,
backgroundColor: '#ffffff',
paddingLeft: 10,
paddingRight: 10,
}
})
export default Input
I have a RN app. Trying to pass fields (in the HEADER) from one page to another (from this "Chat" page TO = "Profile View" page. However, its not working.
I put a Touchable Highlight around it, but not working.
I keep getting Error = cannot find 'first_name'. So I dont think its getting the info INSIDE = render () {.....
You may disregard from 'state' down. Just wanted to show you the page layout.
import React, { Component } from "react";
import {
StyleSheet,
View,
Text,
TouchableHighlight,
Dimensions,
Share,
AlertIOS,
Alert,
TouchableOpacity,
Platform,
KeyboardAvoidingView,
KeyboardSpacer,
StatusBar,
DeviceEventEmitter
} from "react-native";
import { LinearGradient } from 'expo-linear-gradient';
import { GiftedChat } from "react-native-gifted-chat";
import { NavigationActions } from "react-navigation";
import moment from "moment";
import * as firebase from "firebase";
import Email from "../modules/email";
import withPreventDoubleClick from "../components/withPreventDoubleClick";
import CircleImage from "../components/circleImage";
import { Entypo } from "#expo/vector-icons";
import {
ActionSheetProvider,
connectActionSheet
} from "#expo/react-native-action-sheet";
import Modal from "react-native-modal";
const { width, height } = Dimensions.get("window");
const TouchableHighlightEx = withPreventDoubleClick(TouchableHighlight);
import { StackNavigator } from "react-navigation";
#connectActionSheet
class App extends React.Component {
render() {
const first_name = this.props.profile.first_name;
const uid = this.props.user.uid;
const user = this.props.user;
const profile = this.props.profile;
const profileId = this.props.profile.uid;
//const uid = this.props.profile.uid;
//const { id, birthday, first_name, work, imageObj, education } = this.props.navigation.state.params;
return (
<View style={styles.container}>
{Platform.OS === "ios" && (
<Entypo
name="dots-three-vertical"
size={25}
style={{ paddingRight: 7, marginTop: 7 }}
color={"#fff"}
onPress={this._onOpenActionSheet}
/>
)}
{Platform.OS !== "ios" && (
<Entypo
name="dots-three-vertical"
size={25}
style={{ paddingRight: 7, marginTop: 15 }}
color={"#fff"}
onPress={() => this._showModal()}
/>
)}
</View>
);
}
export default class Chat extends Component {
//static navigationOptions = {
static navigationOptions = ({ navigation }) => ({
render() {
const first_name = this.props.profile.first_name;
const uid = this.props.user.uid;
const user = this.props.user;
const profile = this.props.profile;
},
headerTitle: (
<TouchableHighlightEx
underlayColor="transparent"
onPress={() =>
navigation.navigate("ProfileView", {
first_name,
birthday,
id,
uid,
date1,
date2,
date3,
profileUid,
profile: rowData,
user: this.props.navigation.state.params.user
})
}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "center"
}}
>
<CircleImage
size={30}
uri={navigation.state.params.profile.imageObj.img_0}
style={{
width: 32,
height: 32,
resizeMode: "contain",
alignSelf: "center"
}}
/>
<Text style={{ color: "#fff", fontSize: 17, marginLeft: 5 }}>
{navigation.state.params.first_name}
</Text>
</View>
</TouchableHighlightEx>
),
title: navigation.state.params.first_name,
headerTintColor: "#fff",
headerBackTitle: null,
headerRight: (
<ActionSheetProvider>
<App
method={navigation.state.params._showModal}
goBack={navigation.state.params._goBack}
details={navigation.state.params.first_name}
profileId={navigation.state.params.profileUid}
uid={navigation.state.params.user.uid}
user={navigation.state.params.user}
profile={navigation.state.params.profile}
/>
</ActionSheetProvider>
),
headerStyle: {
backgroundColor: "#ff0000",
shadowOpacity: 0,
// iOS
borderBottomWidth: 0,
// Android
elevation: 0
},
});
state = {
messages: [],
user: this.props.navigation.state.params.user,
profile: this.props.navigation.state.params.profile,
modalShow: false,
blockUnblockText: "Block User",
datePlan: null,
isDateLoaded: false,
canLoadEarlier: true,
loadingEarlier: false
};
currentMessage = 20;
messagePerPage = 20;
I am having an issue with the react-native Text tag. I could not figure out why this happening. Please, help out.
I need to use the function retrieveData inside the Text tag but that gives an error. What am I doing wrong? Thanks
I have set the data(mobileNumber as key) using AsyncStorage inside another file.
import React, { Component } from "react";
import {
View,
Text,
TextInput,
SafeAreaView,
TouchableOpacity,
AsyncStorage
} from "react-native";
export default class PhoneCode extends Component {
constructor(props) {
super(props);
this.state = {
};
}
onVerify = () => {};
onPress = () => {
alert("Sending...");
};
tappedVerify = () => {
console.log("clicked")
};
render() {
const { code, isCodeValid } = this.state;
const _retrieveData = async () => {
try {
const value = await AsyncStorage.getItem("mobileNumber");
if (value !== null) {
// We have data!!
return value;
}
} catch (error) {
// Error retrieving data
console.log("failed");
}
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.contentArea}>
<Text style={{}}> Enter... </Text>
<View style={styles.codeArea}>
<Text style={styles.codeInput}> {_retrieveData()} </Text> // this is the problem
<TextInput
placeholder="Enter here"
underlineColorAndroid="transparent"
style={styles.textInput}
maxLength={6}
autoGrow={true}
maxHeight={40}
keyboardType={"phone-pad"}
onChangeText={(text) =>
this.setState({
//....
})
}
/>
</View>
</View>
</SafeAreaView>
Here is the style object
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "flex-start",
alignItems: "center"
},
contentArea: {
justifyContent: "flex-start",
alignItems: "center",
width: '100%',
height: '100%',
paddingTop: 80,
paddingLeft: 20,
paddingRight: 20
},
textInput: {
textAlign: 'center',
height: 50,
width: '18%',
borderWidth: 2,
borderColor: 'gray',
borderRadius: 20,
backgroundColor: "#FFFFFF"
}
})
Here is the error message
ExceptionsManager.js:74 Invariant Violation: Objects are not valid as a React child (found: object with keys {_40, _65, _55, _72}). If you meant to render a collection of children, use an array instead.
Doing this solves my problem. It might help someone else.
<Text style={styles.codeInput}> {`${_retrieveData()}`} </Text>
Transition for google sign in to bottom bar navigation.
For ios and Android
I am following tutorials and creating a react native application that is using google sign in. After the user signs in, the users information is displayed, and there is a logout button. There is also a button that changes the screen the home screen and this is where I need help. I want to implement a bottom bar navigation. The code for the bottom bar navigation works fine, just do not know how to connect the screen from the successful google sign in, to the section of the bottom bar navigation.
my app.js code
//This is an example code for Navigator//
import React, { Component } from 'react';
//import react in our code.
//For react-navigation 4.0+
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import { createStackNavigator} from 'react-navigation-stack';
import { WebView } from 'react-native-webview';
import Ionicons from 'react-native-vector-icons/Ionicons';
import {createBottomTabNavigator} from 'react-navigation-tabs';
import SettingsScreen from './pages/SettingsScreen';
import DetailsScreen from './pages/DetailsScreen';
import ProfileScreen from './pages/ProfileScreen';
import LoginPage from './pages/LoginPage';
const App2 = createStackNavigator({
//Constant which holds all the screens like index of any book
LoginPage: { screen: LoginPage},
Home: { screen: HomeScreen },
},
{
initialRouteName: 'LoginPage',
}
);
const HomeStack = createStackNavigator(
{
//Defination of Navigaton from home screen
Home: { screen: HomeScreen },
Details: { screen: DetailsScreen },
},
{
defaultNavigationOptions: {
//Header customization of the perticular Screen
headerStyle: {
backgroundColor: '#42f44b',
},
headerTintColor: '#FFFFFF',
title: 'Home',
//Header title
},
}
);
const SettingsStack = createStackNavigator(
{
//Defination of Navigaton from setting screen
Settings: { screen: SettingsScreen },
Details: { screen: DetailsScreen },
Profile: { screen: ProfileScreen },
},
{
defaultNavigationOptions: {
//Header customization of the perticular Screen
headerStyle: {
backgroundColor: '#42f44b',
},
headerTintColor: '#FFFFFF',
title: 'Settings',
//Header title
},
}
);
const App = createBottomTabNavigator(
{
Home: { screen: HomeStack },
Settings: { screen: SettingsStack },
},
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, horizontal, tintColor }) => {
const { routeName } = navigation.state;
let IconComponent = Ionicons;
let iconName;
if (routeName === 'Home') {
iconName = `ios-information-circle${focused ? '' : '-outline'}`;
} else if (routeName === 'Settings') {
iconName = `ios-checkmark-circle${focused ? '' : '-outline'}`;
}
return <IconComponent name={iconName} size={25} color={tintColor} />;
},
}),
tabBarOptions: {
activeTintColor: '#42f44b',
inactiveTintColor: 'gray',
},
}
);
export default (App2)
/*
cd "/Users/carloscraig/rnNoExcusasNew" && npx react-native run-ios
cd "/Users/carloscraig/rnNoExcusasNew" && npx react-native run-android
*/
my loginpage.js
/*This is th Example of google Sign in*/
import React, { Component } from 'react';
import QRCode from 'react-native-qrcode-svg';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator} from 'react-navigation-stack';
import {
StyleSheet, ImageBackground, ScrollView, Dimensions,
Text,
View, StatusBar,
Alert,
Image,
ActivityIndicator,
TouchableOpacity,
} from 'react-native';
import {
GoogleSignin,
GoogleSigninButton,
statusCodes,
} from 'react-native-google-signin';
const { height } = Dimensions.get('window');
export default class LoginPage extends React.Component {
static navigationOptions = {
title: 'testing title',
//Sets Header text of Status Bar
headerStyle: {
backgroundColor: '#f4511e',
//Sets Header color
},
headerTintColor: '#fff',
//Sets Header text color
headerTitleStyle: {
fontWeight: 'bold',
//Sets Header text style
},
};
constructor(props) {
super(props);
this.state = {
userInfo: null,
gettingLoginStatus: true,
};
};
// For scrollView
state = {
// We don't know the size of the content initially, and the probably won't instantly try to scroll, so set the initial content height to 0
screenHeight: 0,
};
onContentSizeChange = (contentWidth, contentHeight) => {
// Save the content height in state
this.setState({ screenHeight: contentHeight });
};
componentDidMount() {
//initial configuration
GoogleSignin.configure({
//It is mandatory to call this method before attempting to call signIn()
scopes: ['https://www.googleapis.com/auth/drive.readonly'],
// Repleace with your webClientId generated from Firebase console
webClientId: '.com',
});
//Check if user is already signed in
this._isSignedIn();
}
_isSignedIn = async () => {
const isSignedIn = await GoogleSignin.isSignedIn();
//const { navigate } = this.props.navigation;
if (isSignedIn) {
alert('User is already signed in');
//Get the User details as user is already signed in
this._getCurrentUserInfo();
//navigate('Home')
} else {
alert("Please Login");
console.log('Please Login');
}
this.setState({ gettingLoginStatus: false });
};
_getCurrentUserInfo = async () => {
try {
const userInfo = await GoogleSignin.signInSilently();
console.log('User Info --> ', userInfo);
this.setState({ userInfo: userInfo });
} catch (error) {
if (error.code === statusCodes.SIGN_IN_REQUIRED) {
alert('User has not signed in yet');
console.log('User has not signed in yet');
} else {
alert("Something went wrong. Unable to get user's info");
console.log("Something went wrong. Unable to get user's info");
}
}
};
_signIn = async () => {
//Prompts a modal to let the user sign in into your application.
try {
await GoogleSignin.hasPlayServices({
//Check if device has Google Play Services installed.
//Always resolves to true on iOS.
showPlayServicesUpdateDialog: true,
});
const userInfo = await GoogleSignin.signIn();
console.log('User Info --> ', userInfo);
this.setState({ userInfo: userInfo });
} catch (error) {
console.log('Message', error.message);
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
console.log('User Cancelled the Login Flow');
} else if (error.code === statusCodes.IN_PROGRESS) {
console.log('Signing In');
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
console.log('Play Services Not Available or Outdated');
} else {
console.log('Some Other Error Happened');
}
}
};
_signOut = async () => {
//Remove user session from the device.
try {
await GoogleSignin.revokeAccess();
await GoogleSignin.signOut();
this.setState({ userInfo: null }); // Remove the user from your app's state as well
} catch (error) {
console.error(error);
}
};
render() {
const scrollEnabled = this.state.screenHeight > height;
//returning Loader untill we check for the already signed in user
if (this.state.gettingLoginStatus) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
);
} else {
if (this.state.userInfo != null) {
//Showing the User detail
const { navigate } = this.props.navigation;
return (
<View style={styles.container}>
<StatusBar barStyle="light-content"></StatusBar>
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={styles.scrollview}
scrollEnabled={scrollEnabled}
onContentSizeChange={this.onContentSizeChange}
>
<ImageBackground source={require("/Users/carloscraig/rnNoExcusasNew/pages/assets/grassbcg2.png")}
style={{ width: '100%', height: '100%' }}>
<Image source={require("/Users/carloscraig/rnNoExcusasNew/pages/assets/noexlogo.png")}
style={styles.logo}>
</Image>
<Image
source={{ uri: this.state.userInfo.user.photo }}
style={styles.imageStyle}
/>
<Text style={styles.text}>
Name: {this.state.userInfo.user.name}{' '}
</Text>
<Text style={styles.text}>
Email: {this.state.userInfo.user.email}
</Text>
<View style={styles.qr}>
<QRCode
value= {this.state.userInfo.user.email}
size={200}
/>
</View>
<TouchableOpacity style={styles.button} onPress={this._signOut} >
<Text>Logout</Text>
</TouchableOpacity>
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress={() =>navigate('Home')}>
<Text>Next Page</Text>
</TouchableOpacity>
</View>
</ImageBackground>
</ScrollView>
</View>
);
} else {
const scrollEnabled = this.state.screenHeight > height;
//For login showing the Signin button
return (
<View style={styles.container}>
<StatusBar barStyle="light-content"></StatusBar>
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={styles.scrollview}
scrollEnabled={scrollEnabled}
onContentSizeChange={this.onContentSizeChange}
>
<ImageBackground source={require("/Users/carloscraig/rnNoExcusasNew/pages/assets/grassbcg2.png")}
style={{ width: '100%', height: '100%' }}>
<Image source={require("/Users/carloscraig/rnNoExcusasNew/pages/assets/noexlogo.png")}
style={styles.logo}>
</Image>
<GoogleSigninButton
style={{ width: 312, height: 48 }}
size={GoogleSigninButton.Size.Wide}
color={GoogleSigninButton.Color.Light}
onPress={this._signIn}
/>
</ImageBackground>
</ScrollView>
</View>
);
}
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
width: null,
height: null,
},
imageStyle: {
width: 200,
height: 300,
resizeMode: 'contain',
},
button: {
alignItems: 'center',
backgroundColor: '#DDDDDD',
padding: 10,
width: 300,
marginTop: 30,
marginBottom: 20,
},
scrollview: {
flexGrow: 1,
width: null,
height: null,
},
logo: {
width: 200,
height: 200,
marginTop: 40,
alignSelf: "center",
justifyContent: "center",
},
qr: {
marginTop: 20,
alignSelf: "center",
justifyContent: "center",
},
});
and lastly the homescreen.js
//This is an example code for Bottom Navigation//
import React from 'react';
//import react in our code.
import { Text, View, TouchableOpacity, StyleSheet } from 'react-native';
//import all the basic component we have used
export default class extends React.Component {
//Home Screen to show in Home Option
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ marginTop: 50, fontSize: 25 }}>Home!</Text>
<View
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<TouchableOpacity
style={styles.button}
onPress={() => this.props.navigation.navigate('Settings')}>
<Text>Go to settng Tab</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => this.props.navigation.navigate('Details')}>
<Text>Open Details Screen</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
button: {
alignItems: 'center',
backgroundColor: '#DDDDDD',
padding: 10,
width: 300,
marginTop: 16,
},
});
I have a React-Native flashcard app that boots with two tabs, a Home tab, and a New Deck tab. The Home tab is the default, and you can press or swipe over to the New Deck tab.
The Home tab displays all of the decks the user currently has saved.
On the New Deck tab, I have the user enter the title of their new deck and press a submit button. When that submit button is pressed, I re-navigate to the Home tab.
My issue is: How in the world do I trigger a re-render on the Home tab from a button press on the New Deck tab so the user can see the deck they just created?
I know I could use Redux to solve this issue, but no other part of the app is optimized in a "Redux" fashion, and I'd really like to not redesign the architecture of my app for the sole purpose of updating a single screen, mostly because this is the only instance where I would need this ability.
I've attempted to get around this by passing screenProps containing the this.forceUpdate method all the way from the StackNavigator component, but it didn't work. I also tried manually update the state of the App component to trigger a re-render, but the re-render never happened (although the state did update).
App.js
import React, { Component } from 'react'
import { Text, View } from 'react-native'
import AlphaHome from './Components/Home/AlphaHome'
import AlphaQuiz from './Components/Quiz/AlphaQuiz'
import AlphaNewUdaciDeck from './Components/NewUdaciDeck/AlphaNewUdaciDeck'
import AlphaNewUdaciCard from './Components/NewUdaciCard/AlphaNewUdaciCard'
import AlphaUdaciDeckDetails from './Components/UdaciDeckDetails/AlphaUdaciDeckDetails'
import { TabNavigator, StackNavigator } from 'react-navigation'
const Tabs = TabNavigator({
Home: {
screen: AlphaHome,
navigationOptions: {
tabBarLabel: 'Home',
},
},
NewDeck: {
screen: AlphaNewUdaciDeck,
navigationOptions: {
tabBarLabel: 'New Deck',
}
}
}, {
navigationOptions: {
header: null,
},
tabBarOptions: {
activeTintColor: 'white',
indicatorStyle: {
backgroundColor: 'white'
},
style: {
height: 50,
borderBottomColor: 'white',
backgroundColor: 'deepskyblue',
}
},
})
const Stack = StackNavigator({
Home: {
screen: Tabs,
},
AlphaNewUdaciDeck: {
screen: AlphaNewUdaciDeck,
navigationOptions: {
headerTintColor: 'white',
headerStyle: {
backgroundColor: 'deepskyblue'
}
}
},
AlphaNewUdaciCard: {
screen: AlphaNewUdaciCard,
navigationOptions: {
headerTintColor: 'white',
headerStyle: {
backgroundColor: 'deepskyblue'
}
}
},
AlphaUdaciDeckDetails: {
screen: AlphaUdaciDeckDetails,
navigationOptions: {
headerTintColor: 'white',
headerStyle: {
backgroundColor: 'deepskyblue'
}
}
},
})
export default class App extends Component {
render() {
return (
<Stack />
)
}
}
Home.js
import React, { Component } from 'react'
import { ScrollView, View, Text, StyleSheet, AsyncStorage, ActivityIndicator } from 'react-native'
import UdaciDeck from '../Reusable/UdaciDeck'
import { getAllData } from '../../utils/AsyncApi'
export default class HomeExistingUser extends Component {
state = {
decks: null,
}
componentDidMount() {
let decks = getAllData()
setTimeout(() => {
this.setState({
decks
})
}, 1000)
}
showDetails = (title, count) => {
this.props.navigation.navigate('AlphaUdaciDeckDetails', {title, count})
}
render() {
const {decks} = this.state
return (
decks
? <ScrollView contentContainerStyle={styles.container}>
{decks.map(s => <UdaciDeck key={s[1].title} name={s[1].title} count={s[1].questions.length} method={this.showDetails} />)}
</ScrollView>
: <View style={[styles.container, {flex: 1, justifyContent: 'center'}]}>
<ActivityIndicator size='large' color='white' />
</View>
)
}
}
const styles = StyleSheet.create({
container: {
minHeight: '100%',
backgroundColor: 'lightskyblue',
paddingTop: 20,
paddingBottom: 20,
alignItems: 'center',
},
})
NewDeck.js
import React, { Component } from 'react'
import { View, Text, TextInput, StyleSheet, AsyncStorage, TouchableNativeFeedback, Alert } from 'react-native'
import { addDeck } from '../../utils/AsyncApi'
// BUG: when adding a new deck (if HomeExistingUser is true) view doesn't update. Need to figure out a way to update on tab navigate back
export default class AlphaNewUdaciDeck extends Component {
state = {
input: '',
keys: null,
}
componentDidMount() {
AsyncStorage.getAllKeys()
.then(keys => this.setState({
keys
}))
}
handleSubmit = () => {
const {input, keys} = this.state
input.search(' ') > 0 || input.length < 1 || keys.filter(s => s === input).length > 0
? Alert.alert(`Please enter a valid name (${input.length < 1 || keys.filter(s => s === input).length > 0 ? `you can't save a deck with ${input.length < 1 ? 'no' : 'an already used'} name` : "no spaces"})`)
: addDeck(input)
;if(input.search(' ') < 0 || input.length > 0 || keys.filter(s => s === input).length < 1) {
this.props.navigation.goBack()
}
}
render() {
return (
<View style={[styles.container, styles.containerOne]}>
<View style={styles.containerTwo}>
<Text style={styles.text}>Name of the deck</Text>
<Text style={styles.text}>(Please no spaces)</Text>
<TextInput
autoFocus={true}
onChangeText={(input) => this.setState({
input
})}
selectionColor={'deepskyblue'}
underlineColorAndroid={'transparent'}
style={styles.input}
/>
<TouchableNativeFeedback onPress={this.handleSubmit}>
<View style={styles.btn}>
<Text style={styles.btnText}>Save Deck</Text>
</View>
</TouchableNativeFeedback>
</View>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'lightskyblue',
},
containerOne: {
alignItems: 'center',
},
containerTwo: {
marginTop: 50,
},
text: {
color: 'white',
fontSize: 20,
},
input: {
backgroundColor: 'white',
height: 50,
width: 300,
marginTop: 15,
fontSize: 20,
paddingLeft: 5,
paddingRight: 5,
color: 'deepskyblue'
},
btn: {
backgroundColor: 'deepskyblue',
marginTop: 50,
padding: 20,
paddingLeft: 50,
paddingRight: 50,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 5,
},
btnText: {
color: 'white',
},
})
You should check out react-navigation-is-focused-hoc at https://github.com/pmachowski/react-navigation-is-focused-hoc to solve the specific problem you mentioned.
You can also try
onNavigationStateChange(prevState, newState)
there is a sample at How can I tell if the screen is navigated to with ReactNavigation