I have a simple React Native project in Expo, it launches a website using react-native-webview.
Here is the source code:
import React from "react";
import { StyleSheet, View, SafeAreaView } from "react-native";
import { AntDesign } from "#expo/vector-icons";
import { WebView } from "react-native-webview";
export default function App() {
const goback = () => {
WebView.goBack();
};
return (
<SafeAreaView>
<WebView source={{ uri: "https://google.co.uk" }} />
<View style={styles.navbar}>
<View style={styles.forward}>
<AntDesign name="right" size={25} color="grey" />
</View>
<View style={styles.back}>
<AntDesign name="left" size={25} color="grey" onPress={goback} />
</View>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
navbar: {
height: 40,
width: "100%",
flexDirection: "row-reverse",
paddingTop: 6,
backgroundColor: "#fefefe",
borderTopColor: "grey",
borderTopWidth: 1,
},
back: {
width: 50,
height: 50,
marginRight: 10,
},
forward: {
width: 50,
height: 50,
},
});
The WebView component loads the website fine (google.co.uk) but I can not get the navigation to work. I simply want to create a back button, that allows the user to navigate back to other pages they have viewed in the WebView, and forward if they have gone back and want to go forward.
For now I am trying to get the back button working. I load the app, then navigate to a different page on the WebView. When the back button is pressed, the following error is generated:
TypeError: _reactNativeWebview.WebView.goBack is not a function (In
'_reactNativeWebview.WebView.goBack()','_reactNativeWebview.WebView.goBack'
is undefined)
According to the doc's the goBack() method exists:
goBack()
I found this but it is implementing a class based component so I couldn't easily map the suggestions into my functional component, and further, I think that solution is overkill as they are intercepting the navigation, I believe what I am trying to achieve should be simpler, but I can't get the basic navigation to work on the WebView (i.e. go back and forward to previously viewed pages).
Everything mentioned by you is correct Gary. The only thing that you need to change is the way how goBack function is called. goBack is not a component's direct function rather you need to pass on a reference to the WebView component to get this function. In your case you can change your component as below to get this working:-
import React, { useRef } from "react";
import { StyleSheet, View, SafeAreaView } from "react-native";
import { AntDesign } from "#expo/vector-icons";
import { WebView } from "react-native-webview";
export default function App() {
const webViewRef = useRef(null)
const goback = () => {
webViewRef.current.goBack();
};
return (
<SafeAreaView>
<WebView ref={webViewRef} source={{ uri: "https://google.co.uk" }} />
<View style={styles.navbar}>
<View style={styles.forward}>
<AntDesign name="right" size={25} color="grey" />
</View>
<View style={styles.back}>
<AntDesign name="left" size={25} color="grey" onPress={goback} />
</View>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
navbar: {
height: 40,
width: "100%",
flexDirection: "row-reverse",
paddingTop: 6,
backgroundColor: "#fefefe",
borderTopColor: "grey",
borderTopWidth: 1,
},
back: {
width: 50,
height: 50,
marginRight: 10,
},
forward: {
width: 50,
height: 50,
},
});
This refernce will help you in calling any reference functions mentioned in the documentation of webview module. Enjoy!
also you can see different usage of webview in this package. https://github.com/ilkerkesici/react-native-beauty-webview
Related
My layout is a ScrollView above a footer. When the keyboard isnt open I need the footer to always be visible on the screen:
https://snack.expo.io/#jamesweblondon/privileged-cashew1
export default function App() {
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.content}>
<TextInput style={styles.input} value={"Text Input"} />
</ScrollView>
<View style={styles.footer}>
<Text>Footer</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
backgroundColor: "tomato",
padding: 50,
},
input: {
backgroundColor: "grey",
padding: 50,
},
footer: {
backgroundColor: "green",
padding: 50,
},
});
This works great on iOS. When the keyboard opens you no longer see the footer:
However on Android the footer moves above the keyboard:
Can I stop this behaviour? Ive tried using Keyboard events however keyboardWillShow and keyboardWillHide arn't supported on Android. If I use keyboardDidShow and keyboardDidHide then the delay means the footer is visible as the keyboard animates up and then disappears, which feels jerky and unpleasant.
export default function App() {
const [keyboardIsOpen, setKeyboardIsOpen] = React.useState(false);
Keyboard.addListener("keyboardDidShow", () => {
setKeyboardIsOpen(true);
});
Keyboard.addListener("keyboardDidHide", () => {
setKeyboardIsOpen(false);
});
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.content}>
<TextInput style={styles.input} value={"Text Input"} />
</ScrollView>
{!keyboardIsOpen && (
<View style={styles.footer}>
<Text>Footer</Text>
</View>
)}
</SafeAreaView>
);
}
I also couldn't get it working with the KeyboardAvoidingView. I'm using Expo.
android:windowSoftInputMode already available in EXPO
here is demo: https://snack.expo.io/#nomi9995/01d462
you should give full height to container instead of flex:1
Code:
import React from "react";
import {
StyleSheet,
Text,
View,
ScrollView,
SafeAreaView,
TextInput,
Dimensions
} from "react-native";
const height = Dimensions.get("window").height;
export default function App() {
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.content}>
<View style={{flex:1}}>
<TextInput style={styles.input} value={"Text Input"} />
</View>
</ScrollView>
<View style={styles.footer}>
<Text>Footer</Text>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
height: height
},
content: {
backgroundColor: "tomato",
padding: 50,
},
input: {
backgroundColor: "grey",
padding: 50,
},
footer: {
backgroundColor: "green",
padding: 50
},
});
I found this ready npm package. You can install and use it.
Installation from https://www.npmjs.com/package/react-native-hide-with-keyboard
You just need to cover the component you want to hide, with <HideWithKeyboard>, for example,
<HideWithKeyboard>
<Footer>
<FooterTab>
...More Content Here...
</FooterTab>
</Footer>
</HideWithKeyboard>
To handle this at the code level you can set the footer display property to absolute and bottom:0.
If you want to keep the footer at the bottom specially for the android you can set windowSoftInputMode in the manifest file. Inside the <application> and under <activity> block add the following property.
android:windowSoftInputMode="adjustResize"
then rebuild the app for android, if that still does not work you can also set that to
android:windowSoftInputMode="adjustPan"
It works for me of React-Native version: 0.61.2
android:windowSoftInputMode="adjustPan"
I am trying to learn React native with Ignite. Been fighting with the layout.
Here is my main container render function:
render () {
return (
<View style={styles.mainContainer}>
<Image source={Images.background} style={styles.backgroundImage} resizeMode='stretch' />
<View style={[styles.container]}>
<View style={styles.section} >
{/* <Image source={Images.ready} />*/}
<Text style={styles.sectionText}>
Tap to randomly choose your training task. Slack off for 5
</Text>
</View>
<View style={styles.centered}>
<TouchableOpacity onPress={this._onPressButton}>
<Image source={Images.launch} style={styles.logo} />
</TouchableOpacity>
</View>
</View>
<View style={[styles.bottom]}>
<View >
<BottomBar />
</View>
</View>
</View>
)
}
In particular, the last sibling of the container has a view with a BottomBar component.The bottom style does this:
bottom: {
justifyContent: 'flex-end',
marginBottom: Metrics.baseMargin
}
the BottomBar component:
import React, { Component } from 'react'
// import PropTypes from 'prop-types';
import { View, Text, TouchableOpacity } from 'react-native'
import styles from './Styles/BottomBarStyle'
import Icon from 'react-native-vector-icons/FontAwesome'
export default class BottomBar extends Component {
// // Prop type warnings
// static propTypes = {
// someProperty: PropTypes.object,
// someSetting: PropTypes.bool.isRequired,
// }
//
// // Defaults for props
// static defaultProps = {
// someSetting: false
// }
render () {
console.tron.log('rendering my component')
console.tron.log('Bottom bar styles: \n',styles)
return (
<View style={[styles.iconsContainer, styles.debugGreen]}>
<TouchableOpacity style={[styles.icons,styles.debugYellow]} onPress={()=>{console.tron.log('rocket')}} >
<Icon style={styles.icons} name='rocket' size={40} color='white' />
</TouchableOpacity>
<TouchableOpacity style={styles.button} onPress={ ()=>{console.tron.log('send')} }>
<Icon style={styles.icons} name='send' size={40} color='white' />
</TouchableOpacity>
</View>
)
}
}
the styles associated with it:
import { StyleSheet } from 'react-native'
import DebugStyles from '../../Themes/DebugStyles'
import { Metrics } from '../../Themes/'
export default StyleSheet.create({
...DebugStyles,
iconsContainer: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-between',
height: 45,
borderRadius: 5,
marginHorizontal: Metrics.section,
marginVertical: Metrics.baseMargin
},
icons:{
height: 45
}
})
The issue I have, is that if I saw that bottomBar component for a Rounded button as such:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { TouchableOpacity, Text } from 'react-native'
import styles from './Styles/RoundedButtonStyles'
import ExamplesRegistry from '../Services/ExamplesRegistry'
// Note that this file (App/Components/RoundedButton) needs to be
// imported in your app somewhere, otherwise your component won't be
// compiled and added to the examples dev screen.
// Ignore in coverage report
/* istanbul ignore next */
ExamplesRegistry.addComponentExample('Rounded Button', () =>
<RoundedButton
text='real buttons have curves'
onPress={() => window.alert('Rounded Button Pressed!')}
/>
)
console.tron.log('Rounded button style: ',styles)
export default class RoundedButton extends Component {
static propTypes = {
onPress: PropTypes.func,
text: PropTypes.string,
children: PropTypes.string,
navigator: PropTypes.object
}
getText () {
const buttonText = this.props.text || this.props.children || ''
return buttonText.toUpperCase()
}
render () {
console.tron.log('roundedButton styles:', styles)
return (
<TouchableOpacity style={styles.button} onPress={this.props.onPress}>
<Text style={styles.buttonText}>{this.getText()}</Text>
</TouchableOpacity>
)
}
}
with its styles:
import { StyleSheet } from 'react-native'
import { Fonts, Colors, Metrics } from '../../Themes/'
export default StyleSheet.create({
button: {
height: 45,
borderRadius: 5,
marginHorizontal: Metrics.section,
marginVertical: Metrics.baseMargin,
backgroundColor: Colors.fire,
justifyContent: 'center'
},
buttonText: {
color: Colors.snow,
textAlign: 'center',
fontWeight: 'bold',
fontSize: Fonts.size.medium,
marginVertical: Metrics.baseMargin
}
})
I get the expected view :
However, with my BottomBar component I get:
One thing to notice is that the debugGreen style is just a border that should wrap around my BottomBar component and it is shown flat, but the icons within it render lower, and the debugYellow styled box around the icon is shown around the icon as expected, just shifted a whole way down.
If your mainContainer's view is flex : 1 or height : 100%, you should divide the child's height by 8:2 or the flex by 8:2.
Example
<View style={styles.mainContainer}> // flex: 1
<View style={styles.container}> // flex : 0.8
...
</View>
<View style={styles.bottom}> // flex : 0.2
<BottomBar />
</View>
</View>
I'm new to react native, I am trying to get a menu composed of logos that someone could just scroll down then tap one to go into more detail about it.
So I have my App.js file like so:
import React from 'react';
import {
StyleSheet,
View,
Image,
ScrollView,
Text
} from 'react-native';
import getImageForRestaurant from './utils/getImageForRestaurant';
import Avatar from './components/Avatar';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
restaurants: 'buffalo',
};
}
render() {
const {
restaurants
} = this.state;
return (
<View style={styles.appContainer}>
<View style={styles.titleContainer}>
<Text style={styles.title}>Title</Text>
</View>
<ScrollView style={styles.timerlist}>
<Avatar
initials="KC"
size={75}
source={getImageForRestaurant(restaurants)}
backgroundColor={'blue'}
onPressLinkImage={() => {
console.log('Pressed!');
}}
/>
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
appContainer: {
flex: 1,
justifyContent: 'center',
},
titleContainer: {
paddingTop: 35,
paddingBottom: 15,
borderBottomWidth: 1,
borderBottomColor: '#D6D7DA',
},
title: {
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
timerList: {
paddingBottom: 15,
},
container: {
flex: 1,
backgroundColor: '#34495E',
},
imageContainer: {
flex: 0,
},
image: {
flex: 1,
width: 75,
height: 75,
resizeMode: 'contain',
},
});
The getImageForRestaurant() method works as intended if I make it inside an <Image/> but if I try to make it the source of my "Avatar" component then it won't work.
My getImageForRestaurant.js file is just this:
const images = {
buffalo1: require('../assets/restaurants/logo1.jpeg'),
buffalo: require('../assets/restaurants/logo2.png'),
buffalo2: require('../assets/restaurants/logo3.jpeg'),
};
export default restaurants => images[restaurants];
And finally my Avatar.js is as follows:
import {
ColorPropType,
StyleSheet,
Text,
View,
Image,
TouchableOpacity
} from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
import getImageForRestaurant from '../utils/getImageForRestaurant';
export default function Avatar({
size,
backgroundColor,
initials,
source,
onPressLinkImage,
}) {
const style = {
width: size,
height: size,
borderRadius: size / 2,
backgroundColor,
};
return (
<View style={[styles.container, style]}>
<TouchableOpacity onPress={onPressLinkImage}>
<Text style={styles.text}>{initials}</Text>
<Image source={require(getImageForRestaurant(source))} />
{/*<Image source={require("../assets/restaurants/logo1.jpeg")} />*/}
</TouchableOpacity>
</View>
);
}
Avatar.propTypes = {
initials: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
source: PropTypes.number.isRequired,
backgroundColor: ColorPropType.isRequired,
onPressLinkImage: PropTypes.func.isRequired,
};
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
text: {
color: 'white',
},
});
So if I just do an Image source, (the commented part) it works as a regular image, but then I need to hard-code the actual url and what I want is to just load all images one next to the other in a scrollable grid. Haven't been able to figure out how to do what I want. Could someone please point me in the right direction?
While Edison makes a good point about good practices, I believe your problem is that you are just requiring the image twice. The output of the require() is what you need to pass to the Image component. You are doing require of a require.
<Image source={require(getImageForRestaurant(source))} />
Probably just changing to this should work:
<Image source={getImageForRestaurant(source)} />
It’s a bad practice to generate url inside the source prop. Always make sure that the necessary URL is built before its passed inside source prop. You can use a variable to build your URL and then pass it to source prop. (In your case, image is imported inside helper function and hence I will use image variable)
const image = getImageforRestaurant(source)
<Image source={image} />
When you want to load images from the internet do it like this.
const link = ‘http://example.com/image.png’
<Image source={{uri: link}} />
I'm trying to get a modal working to look like the picture below. I've tried various modal and actionsheet solutions but can't quite get it right. Does anyone know if a solution exists that can provide a similar result? Thanks]1
I have been using an action sheet from a library (pic below) but it cannot be customized to scroll horizontally and use custom buttons. I also have not yet attempted in creating my own, I first wanted to know if anyone knows of a component which will yield the same result.
Regular action sheet on iOS
Here is a simple working example as per your requirement. I am using react-native-modal for Modal component.
import React, { Component } from 'react'
import {
StyleSheet,
Text,
View,
TouchableOpacity,
ScrollView
} from 'react-native'
import Modal from 'react-native-modal'
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
visible: false
}
}
showModal = () => this.setState({visible: true})
hideModal = () => this.setState({visible: false})
render() {
return (
<View style={{flex: 1}}>
<TouchableOpacity
onPress={this.showModal}
style={{alignSelf: 'center', marginTop: 50, backgroundColor: 'grey'}}
>
<Text>Touch Me</Text>
</TouchableOpacity>
<Modal
style={styles.modal}
isVisible={this.state.visible}
onBackdropPress={this.hideModal}
>
<ScrollView
horizontal={true}
>
{/* place your buttons here */}
<Text> Very Very Long String </Text>
</ScrollView>
</Modal>
</View>
)
}
}
const styles = StyleSheet.create({
modal: {
margin: 0,
backgroundColor: 'white',
height: 100,
flex:0 ,
bottom: 0,
position: 'absolute',
width: '100%'
}
})
For some reason, my react-native webview doesn't show up at all. Nothing shows up after my text field. Here's my code
import React, { Component } from 'react';
import {
View,
Text,
WebView,
} from 'react-native';
export default class Home extends Component {
render() {
return (
<View style={{flexDirection:'column'}}>
<Text>Show webview</Text>
<WebView source={{html:"<html><body style='color:red'>Hello<br/>This is a test</body></html>"}} style={{width:200,height:200,backgroundColor:'blue',marginTop:20}} />
</View>
);
}
}
What am I doing wrong?
Add flex: 1 to your <View /> component.
<View style={{flex: 1, flexDirection:'column'}}>
<Text>Show webview</Text>
<WebView source={{html:"<html><body style='color:red'>Hello<br/>This is a test</body></html>"}} style={{width:200,height:200,backgroundColor:'blue',marginTop:20}} />
</View>
Here's a demo
According to the ReactNative docs as of (Nov 2019) they say this:
Warning Please use thereact-native-community/react-native-webviewfork of this component instead. To reduce the surface area of React Native, <WebView/> is going to be removed from the React Native core. For more information, please read The Slimmening proposal.
I wasn't able to get the ReactNative <WebView/> to work at all in my Expo app. So switching to the react-native-community/react-native-webview <WebView/> component worked for me. I hope that helps!
Try out this:
import { WebView } from 'react-native-webview';
export default AppWebview = () => (
<View style={styles.container}>
<WebView
source={{uri: 'https://www.youtube.com/embed/MhkGQAoc7bc'}}
style={styles.video}
/>
<WebView
source={{uri: 'https://www.youtube.com/embed/PGUMRVowdv8'}}
style={styles.video}
/>
</View>
);
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'space-between',
},
video: {
marginTop: 20,
maxHeight: 200,
width: 320,
flex: 1
}
});