How Reload WebView on react navigation - react-native

How I can reload a webview to initial source when I click on button from navigator?
I can reset a original componet using?
React.useEffect(() => {
const unsubscribe = navigation.addListener('tabPress', e => {
});
Or I can reload the webview on component?
function TabOneNavigator({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('tabPress', e => {
alert('Default behavior prevented');
});
return unsubscribe;
}, [navigation]);
return (
<TabOneStack.Navigator>
<TabOneStack.Screen
name="Perfil"
component={TabOneScreen}
options={{ headerTitle: 'IzyJob' }}
/>
</TabOneStack.Navigator>
);
}
my screen
export default class App extends React.Component {
state = {
url: 'https://www.google.com'
};
render() {
return <WebView
source={{ uri: this.state.url }}
style={{ marginTop: 0 }} />;
}
}
My idea is when page on webview is different of state.url and has a click on tab navigation I reload to initial url

First create reference for the webview, like given below
<WebView
ref={(ref) => { this.webview = ref; }}
source={{ uri: this.state.url }}
style={{ marginTop: 0 }} />;
Then call this.webview.reload() where you want, this code will help you to refresh your webview

Related

how do you recognize touch events on react native webview?

Why is longpress event not recognizing the long press event? I have also used gestureHandler component and both are not working.
Can any one point me to relevant documentation or tell me why this code is not recognizing touchevents on the webview component.
export default function SiteCard({route, navigation}) {
const { site } = route.params;
const [urls, setUrls] = useState({
url2: site,
})
const webViewRef = useRef();
const onLongPress = (event) => {
if (event.nativeEvent.state === State.ACTIVE) {
alert("I've been pressed for 800 milliseconds");
}
};
return (
<LongPressGestureHandler
onHandlerStateChange={onLongPress}
minDurationMs={200}
>
<ScrollView scrollEnabled={false}
style={styles.tab}
level='1'>
<View>
<WebView
source={{ uri: urls.url2 }}
style={styles.PageView}
ref={webViewRef}
/>
</View>
</ScrollView>
</LongPressGestureHandler>
);
};
const styles = StyleSheet.create({
PageView: {
height: 800,
}
});

Is there any way to have a permanent element between transitions in React Navigation with Native Stack?

I am trying to replicate the Turbo Native behavior. I have a WebView, and every time a link is visited I want to make a transition and add it to the navigation stack. The problem is that when I do that with React Navigation, it re-renders from the beginning the WebView again for each screen. I would like to reuse the same WebView instance between screens when doing push and pop so I don't have to reload all the assets and javascript over and over again. Is there any way to do this? Thanks!
This is a sample code:
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Web View"
onPress={() => navigation.navigate('WebView', {
url: "https://someurl/"
})}
/>
</View>
);
}
function WebViewScreen({ route, navigation }) {
const webViewRef = useRef(null);
const [URL, setURL] = useState("");
const [title, setTitle] = useState("");
useFocusEffect(
useCallback(() => {
// Do something when the screen is focused
setURL(route.params.url);
setTitle(route.params.title);
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, [])
);
useEffect(() => {
if (URL && URL != route.params.url) {
navigation.push(route.name, {
url: URL,
title: title,
});
}
}, [URL]);
useEffect(() => {
if (title && !route.params.title) {
navigation.setOptions({ title: title })
} else {
navigation.setOptions({ title: route.params.title })
}
}, [title]);
const handleNavigation = (syntheticEvent) => {
const { url, title } = syntheticEvent.nativeEvent;
if (!url) return;
setURL(url);
setTitle(title);
};
const runFirst = `
setTimeout(function() { window.ReactNativeWebView.postMessage("Hello!") }, 2000);
true; // note: this is required, or you'll sometimes get silent failures
`;
return (
<Freeze freeze={!webViewRef}>
<WebView
ref={webViewRef}
style={styles.container}
source={{ uri: route.params.url }}
originWhitelist={['*']}
allowingReadAccessToURL={'*'}
allowFileAccess
allowFileAccessFromFileURLs
allowUniversalAccessFromFileURLs
javaScriptEnabled
javaScriptCanOpenWindowsAutomatically
domStorageEnabled
mixedContentMode={'always'}
onLoadEnd={handleNavigation}
injectedJavaScript={runFirst}
onMessage={(event) => {
alert(event.nativeEvent.data);
}}
/>
</Freeze>
);
}
const HomeStack = createNativeStackNavigator();
function HomeStackScreen() {
return (
<HomeStack.Navigator>
<HomeStack.Screen name="Inicio" component={HomeScreen} />
<HomeStack.Screen
name="WebView"
component={WebViewScreen}
initialParams={{
url: "https://someurl/"
}}
/>
</HomeStack.Navigator>
);
}
The injectedJavaScript property injects javascript on the first load of the webview. Here, on each request it is executing that code, and it should only do so once. This is a way to test it.
I think we can use the history of the web through message communication.
Example
const HTML = `
<!DOCTYPE html>
<html>
<head>
<title>Local File</title>
<script>
function onPress(){
var data = {
"type": "back",
"params": {}
}
window.ReactNativeWebView.postMessage(JSON.stringify(data));
}
</script>
</head>
<body>
<h1>TBAF Test Page</h1>
<form>
<input id="but1" class="input" type="button" value="Checkout" onclick="onPress()" />
</form>
<p id="demo"></p>
</body>
</html>`;
const webviewRef = useRef(null)
return (
<WebView
ref={webviewRef}
source={{ html: HTML }}
style={{ marginTop: 20 }}
onMessage={(event) => {
const {type, params} = event.nativeEvent.data
switch(type) {
case "back":
webviewRef.current.goBack()
break;
}
}}
/>
);

Application wide Modal in React Native

I'm currently using react native modal and it serves the purpose of showing modals.
My problem currently is that I want to show the modal application wide. For example when a push notification received I want to invoke the modal regardless of which screen user is in. The current design of the modals bind it to a single screen.
How can this be overcome?
first of all make a context of your modal
const BottomModal = React.createContext();
then provide your modal using reactcontext provider
export const BottomModalProvider = ({children}) => {
const panelRef = useRef();
const _show = useCallback((data, type) => {
panelRef.current.show();
}, []);
const _hide = useCallback(() => {
panelRef.current.hide();
}, []);
const value = useMemo(() => {
return {
_show,
_hide,
};
}, [_hide, _show]);
return (
<BottomPanelContext.Provider value={value}>
{children}
<BottomPanel fixed ref={panelRef} />
</BottomPanelContext.Provider>
);
};
here is code for bottom panel
function BottomPanel(props, ref) {
const {fixed} = props;
const [visible, setVisibility] = useState(false);
const _hide = () => {
!fixed && hideModal();
};
const hideModal = () => {
setVisibility(false);
};
useImperativeHandle(ref, () => ({
show: () => {
setVisibility(true);
},
hide: () => {
hideModal();
},
}));
return (
<Modal
// swipeDirection={["down"]}
hideModalContentWhileAnimating
isVisible={visible}
avoidKeyboard={true}
swipeThreshold={100}
onSwipeComplete={() => _hide()}
onBackButtonPress={() => _hide()}
useNativeDriver={true}
style={{
justifyContent: 'flex-end',
margin: 0,
}}>
<Container style={[{flex: 0.9}]}>
{!fixed ? (
<View style={{flexDirection: 'row', justifyContent: 'flex-end'}}>
<Button
style={{marginBottom: 10}}
color={'white'}
onPress={() => setVisibility(false)}>
OK
</Button>
</View>
) : null}
{props.renderContent && props.renderContent()}
</Container>
</Modal>
);
}
BottomPanel = forwardRef(BottomPanel);
export default BottomPanel;
then wrap your app using the provider
...
<BottomModalProvider>
<NavigationContainer screenProps={screenProps} theme={theme} />
</BottomModalProvider>
...
lastly how to show or hide modal
provide a custom hook
const useBottomPanel = props => {
return useContext(BottomPanelContext);
};
use it anywhere in app like
const {_show, _hide} = useBottomModal();
//....
openModal=()=> {
_show();
}
//...
If you are not using hooks or using class components
you can easily convert hooks with class context
https://reactjs.org/docs/context.html#reactcreatecontext
this way you can achieve only showing the modal from within components
another way is store the panel reference globally anywhere and use that reference to show hide from non-component files like redux or notification cases.

Pause video in flatlist when out of view

I have a flatlist showing videos. I want that when a video goes out of the view it should be paused. I am maintaining the pause state in each of the Posts component.
class Posts extends React.PureComponent {
constructor() {
super()
this.state = {
pause: true,
}
return(){
<Video
pause={this.state.pause}
//other props
/>
}
}
I am using react-native-video.
I have tried using onViewableItemsChanged prop of Flatlist but it doesn't change the state.
I tried this .
But it doesn't seem to work for me.
How should I proceed ?
Here is a possible solution using react-native-inviewport. This dependency is only a single index file that contains a component that has a callback when view is in the viewport. It could easily be modified to suit your needs.
I have constructed a very simple app that has a FlatList. The 11th item in the FlatList is a video. That should mean that the video is off the screen when the App renders so the viddeo won't be playing, once the video comes fully into the viewport it should then start playing.
App.js
import * as React from 'react';
import { Text, View, StyleSheet, FlatList } from 'react-native';
import Constants from 'expo-constants';
import VideoPlayer from './VideoPlayer';
export default class App extends React.Component {
state = {
data: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
}
renderItem = ({item, index}) => {
if (index === 10) {
return <VideoPlayer />
} else {
return (
<View style={{height: 100, backgroundColor: '#336699', justifyContent: 'center', alignItems: 'center'}}>
<Text>{index}</Text>
</View>
)
}
}
keyExtractor = (item, index) => `${index}`;
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
}
});
VideoPlayer.js
This is a component that contains the Video component. The video is wrapped in the InViewPort component that has a callback function. The callback returns true when the component it surrounds is completely in the viewport and false when it is not fully in the viewport. The callback calls this.handlePlaying which in turn calls either this.playVideo or this.pauseVideo depending on the boolean value.
import React, {Component} from 'react';
import { View, StyleSheet } from 'react-native';
import { Video } from 'expo-av';
import InViewPort from './InViewPort';
//import InViewPort from 'react-native-inviewport; // this wouldn't work in the snack so I just copied the file and added it manually.
export default class VideoPlayer extends React.Component {
pauseVideo = () => {
if(this.video) {
this.video.pauseAsync();
}
}
playVideo = () => {
if(this.video) {
this.video.playAsync();
}
}
handlePlaying = (isVisible) => {
isVisible ? this.playVideo() : this.pauseVideo();
}
render() {
return (
<View style={styles.container}>
<InViewPort onChange={this.handlePlaying}>
<Video
ref={ref => {this.video = ref}}
source={{ uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4' }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode="cover"
shouldPlay
style={{ width: 300, height: 300 }}
/>
</InViewPort>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center'
}
});
Here is a snack showing it working https://snack.expo.io/#andypandy/video-only-playing-when-in-viewport
I should point out that if the video is not fully in the viewport then it will not play. I am sure some tweaking could be done to react-native-inviewport so that it would play the video if it was partially in the viewport if that is what you wanted, perhaps by passing the height of the video to the InViewPort component.
here is how i simply did the trick
inside my card component that have video
<Video ...
paused={currentIndex !== currentVisibleIndex}
/>
both currentIndex and currentVisibleIndex are passed the component from the FlatList parent
my FlatList pass the renderItem index as currentIndex
<FlatList
data={[...]}
renderItem={({ item, index }) => (
<GalleryCard
{...item}
currentIndex={index}
currentVisibleIndex={currentVisibleIndex}
/>
)}
onViewableItemsChanged={this.onViewableItemsChanged}
viewabilityConfig={{
viewAreaCoveragePercentThreshold: 90
}}
finally my this is how to calculate currentVisibleIndex
please make sure to read viewabilityConfig
onViewableItemsChanged = ({ viewableItems, changed }) => {
if (viewableItems && viewableItems.length > 0) {
this.setState({ currentVisibleIndex: viewableItems[0].index });
}
};
please let me know if this is helpful
I finally did this using redux. Not sure whether it is the right way.
Home.js
_renderItem = ({item, index}) => <Posts item={item} />
viewableItemsChanged = (props) => {
let {changed} = props;
let changedPostArray = [];
changed.map((v,i) => {
let {isViewable, item} = v;
if(!isViewable) {
let {post_id, type} = item;
if(type === 1)
changedPostArray.push(post_id);
}
});
if(changedPostArray.length != 0)
this.props.sendPostToPause(changedPostArray);
}
render() {
return(
<View style={{flex: 1}} >
<FlatList
ref={(ref) => this.homeList = ref}
refreshing={this.state.refreshing}
onRefresh={async () => {
await this.setState({refreshing: true, postsId: [0], radiusId: 0, followingId: 0});
this.getPosts();
}}
onEndReached={async () => {
if(!this.state.isEnd) {
await this.setState({isPaginate: true})
this.getPosts()
}
}}
onEndReachedThreshold={0.2}
removeClippedSubviews={true}
contentContainerStyle={{paddingBottom: 80}}
data={this.state.followersRes}
renderItem={this._renderItem}
viewabilityConfig={this.viewabilityConfig}
onViewableItemsChanged={this.viewableItemsChanged}
/>
</View>
<Footer callback={this.scrollToTopAndRefresh} />
</View>
)
}
export default connect(null, {sendPostToPause})(Home);
Posts.js
class Posts extends React.PureComponent {
constructor(){
super()
this.state: {
pause: false
}
}
componentDidUpdate(prevProps) {
if(prevProps != this.props) {
this.props.postIdArray.map((v) => {
if(v === prevProps.item.post_id) {
this.setState({pause: true})
}
})
}
}
render(){
return(
<Video
pause={this.state.pause}
//Other props
/>
)
}
}
const mapStateToProps = (state) => {
const {postId} = state.reducers;
return {
postIdArray: postId
}
}
export default connect(mapStateToProps, {sendPostToPause})(withNavigation(Posts));
Whenever the viewableItemsChanged is trigger I am adding the changed posts id in an array and calling the action with the array of post ids.
In the Posts component I am checking if the post ids match, if so I am setting the pause state to true.

How to mak FlatList automatic scroll?

Here is what i try i use setInterval function to set a variable content will be changed every second and i find onMomentumScrollEnd can get the position y when scroll the FlatList
And then i am stuck , i thougt event.nativeEvent.contentOffset.y = this.state.content; can let my FlatList automatic scroll. Obviously it is not.
Any one can give me some suggestion ? Thanks in advance.
My data is from an API
Here is my App.js:
import React from 'react';
import { View, Image, FlatList, Dimensions } from 'react-native';
const { width, height } = Dimensions.get('window');
const equalWidth = (width / 2 );
export default class App extends React.Component {
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
this.state = { movies: [], content: 0 };
}
componentWillMount() {
fetch('https://obscure-reaches-65656.herokuapp.com/api?city=Taipei&theater=Centuryasia')
.then(response => response.json())
.then(responseData => {
console.log(responseData);
this.setState({ movies: responseData[0].movie });
})
.catch((error) => console.log(error));
this.timer = setInterval(() => {
this.setState({content: this.state.content+1 })
}, 1000);
}
// get the jsonData key is item and set the value name is movie
renderRow({ item: movie }) {
console.log('renderRow => ');
return (
<View>
<Image source={{ uri: movie.photoHref}} style={{ height: 220, width: equalWidth }} resizeMode="cover"/>
</View>
);
}
render() {
const movies = this.state.movies;
// it well be rendered every second from setInterval function setState
console.log('render');
return (
<View style={{ flex: 1 }}>
<FlatList
data={movies}
renderItem={this.renderRow}
horizontal={false}
keyExtractor={(item, index) => index}
numColumns={2}
onMomentumScrollEnd={(event) => {
console.log(event.nativeEvent.contentOffset.y);
event.nativeEvent.contentOffset.y = this.state.content;
}}
/>
</View>
);
}
}
You need to tell your FlatList that you want it to scroll to a new position using scrollToOffset().
Store a reference to your FlatList in your class by adding the prop
ref={flatList => { this.flatList = flatList }} to it.
Then, call this.flatList.scrollToOffset({ offset: yourNewOffset }) to scroll to the desired offset.
Docs on this method are here.