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
I'm trying to use params on the header of my Image details screen!
Here's a brief explanation.
My user enters an input, I call an api and display the info on the screen:
Home.js
<View style={styles.viewpic}>
<TouchableOpacity onPress={() => navigation.navigate('ImageDetails',
item)}>
<Image
style={{
height: 104,
}}
source={{uri:item.url}}/>
</TouchableOpacity>
</View>
Then, the user clicks on the chosen data, displayed on the screen, and my app navigates to the details page:
ImageDetails.js
export default function ImageDetails({navigation}) {
return(
<ScrollView>
<View>
<Image
source={{uri:navigation.getParam('url')}}/>
<View style={styles.descriptionBox}>
<Text style={styles.imageDet}>Description:{" "}
{navigation.getParam('explanation')}</Text>
</View>
</View>
</ScrollView>
this is the navigation folder I have:
homeStack.js
const screens = {
Home: {
screen: Home,
navigationOptions:{ headerShown: false}
},
ImageDetails: {
screen: ImageDetails,
navigationOptions: () => {
return{
headerTitle: () => <Header/>,
}
}
}
}
const HomeStack = createStackNavigator(screens);
export default createAppContainer(HomeStack);
plus the HEADER component that I'm trying to use in the header navigation (top of the screen):
Header.js
export default function Header({navigation}) {
return(
<View style={styles.descriptionBox}>
<Text style={styles.imageDet}>Params here!</Text>
</View>
)
Here is what the Image detail screen looks like:
My goal is:
to be able to use the data params on the header.
I tried a few different combos but I keep on getting the error: "cant read params"
Some of the things I tried:
Header.js :
export default function Header({navigation}) {
return(
<View style={styles.descriptionBox}>
<Text style={styles.imageDet}>Test:{navigation.getParam('item')}
</Text>
</View>
)
Homestack component:
homeStack.js
const screens = {
Home: {
screen: Home,
navigationOptions:{ headerShown: false}
},
ImageDetails: {
screen: ImageDetails,
navigationOptions: ({navigation}) => {
return{
headerTitle: () => <Header navigation=
{navigation.getParams('title')}/>,
}
}
}
}
const HomeStack = createStackNavigator(screens);
export default createAppContainer(HomeStack);
I also have read the documentation but I'm not sure how I would insert the "Navigation.push" with params here.
Thanks for your help!
try using the existent image details page instead of creating
a new one!
ImageDetails: {
screen: ImageDetails,
navigationOptions: ({navigation}) => {
return{
headerTitle: () => {
return(
<View>
<Text>{navigation.getParam('title')}
</Text>
</View>
)
},
I'm starting with React Native and I would like to open an input search field on a specific screen to filter some results displayed on that screen using the right header icon (search icon).
I was able to use initialParams on Drawer.Screen and I can console.log that param on results screen when the screen loads. I just don't know how to pass this param again (as true) when I click on header icon being on results screen already.
app.routes.tsx:
const Drawer = createDrawerNavigator();
export function AppRoutes() {
const [openFilter, setOpenFilter] = useState(false);
return(
...
<Drawer.Screen
name="Results"
component={Home}
initialParams={{ openFilter: openFilter }}
options={{
headerRight: ({}) => (
<BorderlessButton
onPress={() => {
setOpenFilter(true);
console.log(openFilter);
}}
>
<Feather name="search" size={24} color={theme.colors.heading} style={{ marginRight: 15 }} />
</BorderlessButton>
),
}}
/>;
}
Home.tsx:
export function Home({ route }: any) {
const openFilter = route.params.openFilter;
console.log(openFilter);
const [filter, setFilter] = useState(false); // I would manage filter to be true
return(
...
{filter &&
<View style={{ marginBottom: 24, paddingHorizontal: 10 }}>
<TextInput
style={{ borderBottomColor: 'white', borderBottomWidth: 1 }}
/>
</View>
}
...
I think that if I'd turn openFilterinto true by clicking on header icon I can display the input text. Should I re-render the results screen?
I found a method from navigation that I was using to manage navigation between screens and I can send any params with it:
const Drawer = createDrawerNavigator();
export function AppRoutes() {
const navigation: any = useNavigation();
const [openFilter, setOpenFilter] = useState(false);
return(
...
<Drawer.Screen
name="Results"
component={Home}
initialParams={{ openFilter: openFilter }}
options={{
headerRight: ({}) => (
<BorderlessButton
onPress={() => {
navigation.setParams({ openFilter: openFilter}); // here is the method from navigation
}}
>
<Feather name="search" size={24} color={theme.colors.heading} style={{ marginRight: 15 }} />
</BorderlessButton>
),
}}
/>;
}
And now I can get openFilter or any other param on Home screen.
It's taking me forever to figure out the obvious, would appreciate some help.
Im using a stack navigator, when a button is pressed it will simply go to another page.
In app.js I created a stack navigator:
const Switcher = createStackNavigator(
{
TaskPg: ListScreen,
AboutPg: AboutScreen
},
{
initialRouteName: "TaskPg",
defaultNavigationOptions: {
title: 'BlueList'
}
}
)
In the ListScreen there is a button the user can press to go to the about page.
const ListScreen = () => {
return (
<View style={styles.container}>
{/* add task component with date picker */}
<AddItemModel />
{/* button pressed to goto About Screen */}
<Button
onPress={() => this.props.navigation.navigate(AboutScreen)}
title="About App" />
{/* sign out button linked to firebase log out */}
<TouchableOpacity onPress={() => firebase.auth().signOut()} >
<Text style={styles.button} >Sign Out</Text>
</TouchableOpacity>
</View>
);
}
export default ListScreen
Run code and every time I press the button I get the error undefined is not an object (evaluating this.props.navigation)
Since your ListScreen is a functional component, this.props doesn't exist.
Change your ListScreen declaration to:
const ListScreen = props => {...}
and access your navigation object like this:
props.navigation.navigate(AboutScreen);
I currently have a static navigationOptions on every single page where I override the backbutton. I'm trying to change this so it uses defaultNavigationOptions instead. How do I access the back functionality of the navigationstack from the navigation file itself?
Current code:
const LoginNavigator = createStackNavigator(
{
// screens
},
{
headerLeft: (
<TouchableHighlight
onPress={() => /* navigate back */}
style={{ alignSelf: "center", marginLeft: 10 }}
>
<FontAwesomeIcon icon={faArrowLeft} color="#ffffff" />
</TouchableHighlight>
)
}
}
);
export const LoginNavigation = createAppContainer(LoginNavigator);
This gets imported into the main app, may that help.