i have screen inside my app where the user is required to enter their 3 digit code to continue. When the user, enters their 3 digit code, a modal with a webview inside is opened. The webview then automatically redirects the user to the correct url where parameters are attached to the url based on my backend and updates a status field inside my database.
On ios, i have used onShouldStartLoadWithRequest to listen to changes inside the url then once the desired url is achieved i Alert the user that it was successful and navigate the user back to the home screen.
On android, since onShouldStartLoadWithRequest doesnt work, i have created an API call to my backend to fetch the value of status every 2 secs and then if status is successful, Alert the user.
On ios, everything is working without any crashes.
On android, 2 crashes occur:
after the webview modal is closed, the textinput field crashes and i am no longer able to enter any input inside (even the placeholder is not visable)
after the webview modal is closed, the alert does not appear
Here is my code:
function Screen({navigation,route}) {
let myWebView;
const [uploadVisible, setUploadVisible] = useState(false);
const[cvv,setCvv]=useState('');
const[authenticateWebView,setAuthenticateWebView]=useState(false);
const handlePayment=async()=>{
setUploadVisible(true);
setUploadVisible(false);
setAuthenticateWebView(true)
}
const[statusUpdated,setStatusUpdated]=useState(false);
const intervalRef = useRef();
const everyTwoSeconds=async()=> {
console.log('android')
const response = await transactionsApi.getTransaction(route.params.transaction.id);
console.log(response)
if(response.data.status==='paid'){
setStatusUpdated(true);
setAuthenticateWebView(false);
{authenticateWebView===false?
Alert.alert('Payment successful',"Your card has been charged",
[
{ text: 'Yes',
onPress:()=> {
navigation.navigate(routes.HOME);
}},
{ text: 'Cancel',style:"cancel" },
]):null}
}
}
const myInterval =()=>{
intervalRef.current=setInterval(()=>{
everyTwoSeconds()
}, 5000);
}
useEffect(() => {
const intervalId = intervalRef.current;
// also clear on component unmount
return () => clearInterval(intervalId);
}, []);
useEffect(() => {
if (statusUpdated===true) {
clearInterval(intervalRef.current);
}
}, [statusUpdated]);
const modal = React.createRef();
const [isKeyboardVisible, setKeyboardVisible] = useState(false);
useEffect(() => {
const keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => {
setKeyboardVisible(true); // or some other action
}
);
const keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => {
setKeyboardVisible(false); // or some other action
}
);
return () => {
keyboardDidHideListener.remove();
keyboardDidShowListener.remove();
};
}, []);
const keyboardHeight = useKeyboard();
return(
<>
{
authenticateWebView?
<Modal visible={authenticateWebView}>
<View style={{
flex : 1,
justifyContent : 'center',
alignItems : 'center',
backgroundColor: 'rgba(0,0,0,0)'}}>
<View style={{
flex:1,
width:'100%',
flexDirection:'row',
backgroundColor:colorScheme==='dark'?colors.black:colors.white,
justifyContent:'space-between',
alignItems:'center',
paddingTop:Platform.OS='ios'?40:0
}}>
<TouchableOpacity style={{
backgroundColor:colorScheme==='dark'?colors.black:colors.white,
justifyContent:'flex-start',
alignItems:'center',
paddingLeft:20
}}
onPress={()=>{setAuthenticateWebView(false);clearInterval(myInterval)}}
>
<Text style={{color:colors.primary,fontSize:15,fontWeight:'700'}}>
Cancel
</Text>
</TouchableOpacity>
</View>
<View style={{height : '90%',width:'95%',backgroundColor:colors.white}}>
<WebView
style={{flex:1}}
source={{html:`${url}`}}
ref={el=>myWebView=el}
onNavigationStateChange={(navState) => {
console.log(navState)
}}
/>
</View>
</View>
</Modal>
:null
}
<View style={{
flex:1,
backgroundColor:colorScheme==='dark'?colors.dark:colors.white,
paddingBottom:colorScheme==='dark'?0:0,
marginBottom:0,
borderTopRightRadius:10,
borderTopLeftRadius:10,
paddingTop:0,
marginTop:0
}}>
<View
style={{
backgroundColor:colorScheme==='dark'?colors.black:colors.white,
borderRadius:5,
borderWidth:1,
borderColor:colors.primary,
height:30,
marginTop:10,
marginHorizontal:20
}}>
<TextInput
placeholder={"CVV"}
maxLength={3}
keyboardType={"numeric"}
onChangeText={cvv => setCvv(cvv)}
value={cvv.value}
/>
</View>
<AppButton title="Pay now"
onPress={()=>{
console.log(cvv.length)
if(cvv.length!==3){
return Alert.alert('Please enter your card security code (CVV).',"This can be found on the back of your card.")
}
handlePayment();
Platform.OS!=='ios'?myInterval():null;
}}
/>
</View>
</>
);
}
Before webview submission:
after webview submission
Related
I have logic that makes my home page refresh every time the component is mounted. I do want it to refresh every time I go from the home screen to the profile screen of my app for example, but I do not want it to reload when I open the search modal page and just dismiss it.. or click on an item in the home page and dismiss it.. its a bit excessive. My question is how would I be able to do this in an elegant way where I can pick and choose when I want the page to reload based on which page I'm going back from.
const HomeScreen = (props) => {
let TouchableCmp = TouchableOpacity
if (Platform.OS === 'android' && Platform.Version >= 21) {
TouchableCmp = TouchableNativeFeedback
}
const [isLoading, setIsLoading] = useState(false)
const [httpError, setHttpError] = useState(false)
const homeTickets = useSelector((state) => state.tickets.homeTickets)
const dispatch = useDispatch()
const loadTickets = useCallback(async () => {
setHttpError(false)
setIsLoading(true)
try {
await dispatch(setTickets())
setIsLoading(false)
} catch (err) {
setHttpError(err.message)
setIsLoading(false)
}
}, [dispatch])
useEffect(() => {
const willFocusListener = props.navigation.addListener(
'willFocus',
loadTickets
)
return () => {
willFocusListener.remove()
}
}, [loadTickets])
useEffect(() => {
setIsLoading(true)
loadTickets().then(() => {
setIsLoading(false)
})
}, [dispatch, loadTickets])
if (isLoading)
return (
<View style={styles.center}>
<LottieView
source={require('../assets/srSPININGcompatibility.json')}
style={{ height: 60, width: 60 }}
autoPlay
loop
></LottieView>
</View>
)
if (httpError)
return (
<View style={styles.center}>
<Text style={styles.errorStyle}>{`${httpError}`}</Text>
<View style={styles.refreshCont}>
<TouchableCmp onPress={loadTickets} activeOpacity={0.5}>
<Text
style={{
fontSize: 17,
color: 'white',
fontWeight: 'bold',
}}
>
Refresh
</Text>
</TouchableCmp>
</View>
</View>
)
return (
<View style={styles.screen}>
<View style={styles.screen}>
<FlatList
showsVerticalScrollIndicator={false}
onRefresh={loadTickets}
refreshing={isLoading}
style={styles.flatList}
data={homeTickets}
keyExtractor={(item) => item.ticketID}
renderItem={(itemData) => (
<TicketCell
companyName={itemData.item.company}
requestor={itemData.item.requestedBy}
dateStamp={itemData.item.workLogDate}
issue={itemData.item.issue}
companyPhone={itemData.item.businessPhone}
navigation={props.navigation}
ticketID={itemData.item.ticketID}
goToDetails={() => {
props.navigation.navigate({
routeName: 'HomeDetail',
params: { id: itemData.item.ticketID },
})
}}
/>
)}
/>
</View>
</View>
)
}
I have a profile component which is a tab screen, and here is the code:
class Profile extends React.Component {
constructor(props) {
super(props)
this.state = {
user: this.props.user,
bio: "",
storage_image_uri: '',
postCount: 0,
followerCount: 0,
followingCount: 0,
isLoading: true,
navigation: this.props.navigation,
userUID: Firebase.auth().currentUser.uid,
userPostsArray: []
}
this.firestoreRef =
Firebase.firestore()
.collection('globalPosts')
.where("uid", "==", this.state.userUID)
.orderBy("date_created", "desc");
}
componentDidMount() {
this.pullUserInfo()
this.unsubscribe = this.firestoreRef.onSnapshot(this.getCollection);
}
componentWillUnmount(){
this.unsubscribe();
}
getCollection = (querySnapshot) => {
const userPostsArray = [];
querySnapshot.forEach((res) => {
const {
...
} = res.data();
userPostsArray.push({
...
});
});
this.setState({
userPostsArray,
isLoading: false,
});
this.pullUserInfo()
}
pullUserInfo = async() => {
await Firebase.firestore()
.collection('users')
.doc(this.state.userUID)
.onSnapshot(function(doc){
if (doc.exists) {
console.log("pulling info")
this.setState({
postCount: doc.data().postCount,
followerCount: doc.data().followerCount,
followingCount: doc.data().followingCount,
storage_image_uri: doc.data().profilePic,
bio: doc.data().bio,
isLoading: false
})
} else {
console.log("No such document!");
}
}.bind(this))
}
gotToSettings() {
this.state.navigation.navigate('Settings')
}
//How I show my profile stats, follower, following, bio, profile pic
renderListHeader = () => {
return (
<View style = {styles.container}>
<View style={{ flexDirection: "row", padding: 20 }}>
<Text style = {styles.subheader}> {this.state.user.username} </Text>
</View>
<View style={{ flex: 1, flexDirection: "row", alignItems: 'center',
justifyContent: 'center',}}>
<ProfilePic storage_image_uri = {this.state.storage_image_uri} />
<ProfileStats postCount = {this.state.postCount} followerCount = {this.state.followerCount} followingCount = {this.state.followingCount}/>
</View>
<View style = {styles.lineStyle} />
<ProfileBio bio = {this.state.bio}/>
<TouchableOpacity
onPress={() => this.gotToSettings()}>
<Ionicons name="ios-settings" size={35} color="black" />
</TouchableOpacity>
</View>
)
}
render() {
const { navigation } = this.props;
const renderItem = ({ item }) => (
<CurrentUserPostCell
...
/>
);
if(this.state.isLoading){
return(
<View styles = {styles.container}>
<ActivityIndicator size="large" color="#9E9E9E"/>
</View>
)
}
return (
<View>
<FlatList
data={this.state.userPostsArray}
renderItem={renderItem}
keyExtractor={item => item.key}
ListHeaderComponent={this.renderListHeader}
contentContainerStyle={{ paddingBottom: 50 }}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
/>
</View>
)
}
}
As you can see, when the component mounts, pullUserInfo() is called, as well as the collection of user posts is fetched. There is no issue here getting this information for the first time.
My problem is, when I post something new, or follow someone new, the post count and following count goes up respectively in Firestore. But the new numbers are not reflected in my profile.
It is worth noting, when I create a new post, the new post is displayed in on the user profile. But even this event does not update pullUserInfo()
So, the information is not updating. Furthermore, when I click on the profile tab, I get the following warning:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method
How can I fix this, so that if the user information in Firestore changes, the changes update in the profile?
EDIT: I partly fixed my problem by adding "pull down to refresh" logic to the flatlist, since the flatlist header is the user profile information, and the flatlist data is the list of user posts.
At the end of flatlist in my render():
return (
<View>
<FlatList
data={this.state.userPostsArray}
renderItem={renderItem}
keyExtractor={item => item.key}
ListHeaderComponent={this.renderListHeader}
contentContainerStyle={{ paddingBottom: 50 }}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
onRefresh={this._refresh}
refreshing={this.state.isLoading}
/>
</View>
)
Outside the render:
_refresh = () => {
this.setState({ isLoading: true });
this.pullUserInfo()
};
This doesn't solve the full problem of the profile component only mounting once, therefore not refreshing the user stats automatically. The user has to manually pull down to refresh their profile if they want to see an updated bio, follower/following count, etc.
Here in my code I am making tree tabs , on first tabe there are two input fields and buttons.
Now after entering the value in input and on button click i have to send vale to oter tabs.
Like in in name field I am entering name "Abhi" and on button click this Abhi should reflect on Tab 2.
Same like in Animal field , this Animal should reflect on third tab .
Please help
import * as React from 'react';
import { View, StyleSheet, Dimensions,Text,TextInput,TouchableOpacity } from 'react-native';
import { TabView, SceneMap } from 'react-native-tab-view';
const FirstRoute = () => (
<View style={[styles.scene, { backgroundColor: '#FFFFFF' }]} >
<View style={{}}>
<Text style={{margin:15}}>Name </Text>
<TextInput style={styles.input}
underlineColorAndroid = "transparent"
placeholder = "Name"
placeholderTextColor = "#9a73ef"
autoCapitalize = "none"
onChangeText={text => onChangeText(text)}
/>
<TouchableOpacity
style = {styles.submitButton}
onPress = {
() => this.Name()
}>
<Text style = {styles.submitButtonText}> Submit </Text>
</TouchableOpacity>
</View>
<View style={{}}>
<Text style={{margin:15}}> Favorite Animal </Text>
<TextInput style={styles.input}
underlineColorAndroid = "transparent"
placeholder = "Favorite Animal"
placeholderTextColor = "#9a73ef"
autoCapitalize = "none"
onChangeText={text => onChangeText(text)}
/>
<TouchableOpacity
style = {styles.submitButton}
onPress = {
() => this.Animal()
}>
<Text style = {styles.submitButtonText}> Submit </Text>
</TouchableOpacity>
</View>
</View>
);
const SecondRoute = () => (
<View style={[styles.scene, { backgroundColor: '#FFFFFF' }]} >
<Text> {'Name' }</Text>
</View>
);
const ThirdRoute = () => (
<View style={[styles.scene, { backgroundColor: '#FFFFFF' }]} >
<Text> {"Favorite Animal "}</Text>
</View>
);
const initialLayout = { width: Dimensions.get('window').width };
export default function TabViewExample() {
const [index, setIndex] = React.useState(0);
const [routes] = React.useState([
{ key: 'first', title: 'First' },
{ key: 'second', title: 'Second' },
{ key: 'third', title: 'Third' },
]);
const renderScene = SceneMap({
first: FirstRoute,
second: SecondRoute,
third:ThirdRoute
});
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={initialLayout}
/>
);
}
const styles = StyleSheet.create({
scene: {
flex: 1,
},
container: {
paddingTop: 23
},
input: {
margin: 15,
height: 40,
borderColor: '#7a42f4',
borderWidth: 1
},
submitButton: {
backgroundColor: '#65D370',
padding: 10,
margin: 15,
height: 40,
},
submitButtonText:{
color: 'white',
alignSelf:'center',
justifyContent:'center',
borderRadius:20
}
});
Shortest answer, is try to use a state. Using states and passing the state from parent to child may be your best option. Here is one way you can go about it.
1st in your TabViewExample add a useState() hook to keep the form data and change your renderScene() to a function, do not use SceneMap. Example:
...
const [name, setName] = React.useState(undefined);
const renderScene = ({ route }) => {
switch (route.key) {
case "first":
return <FirstRoute setName={setName} />;
case "second":
return <SecondRoute name={name} />;
case "third":
return <ThirdRoute />;
default:
<FirstRoute setName={setName} />;
}
};
(A) The reason for using renderScene() as function is explained with more detail on the "react-native-tab-view" documentation. In short when you need to pass props to components you should not use SceneMap() which you are using above instead turn renderScene into a function and use switch.
(B) We only passed setName to the first component because that's what we'll be using.
2nd - Make use of the props in your components. So now they'll look more or less like this:
const FirstRoute = props => (
<View style={[styles.scene, { backgroundColor: "#FFFFFF" }]}>
<View style={{}}>
<Text style={{ margin: 15 }}>Name </Text>
<TextInput
style={styles.input}
underlineColorAndroid="transparent"
placeholder="Name"
placeholderTextColor="#9a73ef"
autoCapitalize="none"
onChangeText={text => props.setName(text)}
/>
...
And for the SecondRoute :
const SecondRoute = props => (
<View style={[styles.scene, { backgroundColor: "#FFFFFF" }]}>
<Text> {props.name}</Text>
</View>
);
So now when you change the first Input in FirstRoute, the name state will automatically be updated, so when you go/swipe to page 2, you should see whatever you typed on the first TextInput on page 1.
PS: this is just a brief explanation so I just gave you the essential idea behind sharing data across tabs/components. On your code you can create cleaner form handler functions and handler functions for those buttons. I could've done it, but I'll leave that job for you as it was not part of your initial question. Hope this helps and let me know if you need a more detailed/in-depth response.
PS 2: If you use my current example don't click the buttons otherwise you'll get errors because you have no handler function, just type on the input and go to the second page to see the result.
in my app on my homescreen I am rendering a ListList with my items. Now I wanted to add a little info text above it. But it only shows my text and skips(?) my FlatList. Could anyone help me and explain me this behaviour?
If I have my text component in my file it only shows the text. If I only use FlatList it shows correctly my list with my data. But if I try to combine both, it only shows one component. Same thing when I use only FlatList and wrap it inside of View then I get only a white blank screen.
const JobsScreen = (props) => {
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const allJobs = useSelector((state) => state.job.availableJobs);
const loadJobs = useCallback(async () => {
setIsRefreshing(true);
try {
await dispatch(jobActions.fetchAllJobs());
} catch (err) {}
setIsRefreshing(false);
}, [dispatch]);
useEffect(() => {
setIsLoading(true);
loadJobs().then(() => {
setIsLoading(false);
});
}, [dispatch, loadJobs]);
useEffect(() => {
const willFocusSub = props.navigation.addListener("willFocus", loadJobs);
return () => {
willFocusSub.remove();
};
}, [dispatch, loadJobs]);
if (isLoading) {
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#2f3640",
}}
>
<ActivityIndicator size="large" color="#fff" animating />
</View>
);
}
return (
<View>
<FlatList
data={allJobs}
onRefresh={loadJobs}
refreshing={isRefreshing}
keyExtractor={(item) => item.id}
style={{ flex: 1, backgroundColor: "#1a191e" }}
renderItem={(itemData) => (
<JobItem
description={itemData.item.description}
titel={itemData.item.titel}
fname={itemData.item.fname}
cover={itemData.item.cover}
genre={itemData.item.genre}
year={itemData.item.year}
id={itemData.item.id}
// friend={itemData.item.friend}
/>
)}
/>
</View>
);
};
Got it by myself.
<View style={{ height: "100%" }}>
solved it.
Try this.
<View style={{ flex: 1 }}>
I want the function that detects if there is a saved token to be verified, every time the component called login is executed.In login I have a function that verifies if a token exists, and if it exists automatically redirects to thehome view, otherwise it will remain in the login view.
Login
const Login = props => {
const [loading, setLoading] = useState(true);
useEffect(() => {
getTokenPrevious();
}, [loading]);
const getTokenPrevious = () => {
AsyncStorage.multiGet(["token"])
.then(value => {
let token = value[0][1];
if (token !== null) {
props.navigation.navigate("home");
} else {
setLoading(false);
}
})
.catch(error => {
setLoading(false);
});
};
if (loading) {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Loading...</Text>
<Spinner color={STYLES.bgHeader.backgroundColor} />
</View>
);
}
return (
rest code login....
Sometimes when from the home view I use thebackbutton of the cell phone or when I try to tap on the logout button, this redirects me to thelogin view but this part is shown:
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Loading...</Text>
<Spinner color={STYLES.bgHeader.backgroundColor} />
</View>
);
the part that should be shown is this:
return (
rest code login....
because the token no longer exists because it was deleted.
home
const Home= props => {
clearStorage = () => {
AsyncStorage.removeItem("token")
.then(() => {
props.navigation.navigate("Login");
})
};
return (
<View>
<Button onPress={clearStorage()} ><Text>Logout</Text></Button>
<View>
)
}
How can i fix this?
Even if you have deleted the token, this change will not be reflected in Login component, therefore loading will sitll be true and that's why useEffect is not called. Try listening didFocus or willFocus event on Login component and check again if token exists
useEffect(()=>{
getTokenPrevious()
const focusListener = props.navigation.addListener('didFocus', () => {
getTokenPrevious()
})
return ()=>focusListener.remove()
},[])
Instead of
useEffect(() => {
getTokenPrevious();
}, [loading]);