Instantly share code, notes, and snippets.
Forked from mshivam019/ScreenWebview.jsx
Last active
March 10, 2025 15:09
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save rachidelaid/bc608982aa3d6a2a770e3ab499b729a5 to your computer and use it in GitHub Desktop.
A feature rich webview for react native
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {useState, useEffect, useRef, useCallback} from 'react'; | |
import {View,Text,BackHandler, Share as RNshare, Platform} from 'react-native'; | |
import {WebView} from 'react-native-webview'; | |
import {useRoute, useNavigation} from '@react-navigation/native'; | |
import {useFocusEffect, useIsFocused} from '@react-navigation/native'; | |
import Share from 'react-native-share'; | |
import ReactNativeBlobUtil from 'react-native-blob-util'; | |
import styled from 'styled-components/native'; | |
import axios from 'axios'; | |
import HeaderComponentWithBackButton from '../components/HeaderComponentWithBackButton'; | |
import NetworkErrorModalWithRetryButton from '../components/NetworkErrorModalWithRetryButton'; | |
import Loader from '../components/Loader'; | |
import WarningIcon from '../icon/WarningIcon'; | |
const Toast = styled.View` | |
position: absolute; | |
bottom: 20px; | |
padding: 10px; | |
border-radius: 8px; | |
align-self: center; | |
background-color: #000; | |
justify-content: center; | |
align-items: center; | |
z-index: 999; | |
`; | |
const LoaderContainer = styled(View)` | |
height: 100%; | |
width: 100%; | |
justify-content: center; | |
align-items: center; | |
background-color: #fff; | |
`; | |
export default function ScreenWebview({}) { | |
const route = useRoute(); | |
const navigation = useNavigation(); | |
const webviewRef = useRef(null); | |
const isFocused = useIsFocused(); | |
const [url, setUrl] = useState(route.params?.url ? route.params.url : ''); | |
const [showNetworkModal, setShowNetworkModal] = useState(false); | |
const [showToast, setShowToast] = useState(false); | |
// State to trigger a re-render of the webview | |
const [webviewKey, setWebviewKey] = useState(false); | |
const canGoBackRef = useRef(false); | |
const currentUrlRef = useRef(''); | |
const injectedJs = ` | |
(function(){ | |
console.log('injected js'); | |
})(); | |
`; | |
const onMessage = async (event) => { | |
let eventData = {}; | |
try { | |
eventData = JSON.parse(event.nativeEvent.data); | |
} catch (error) { | |
// not a json string | |
} | |
if (event.nativeEvent.data === 'someEvent') { | |
//action for that event | |
} else if (event.nativeEvent.data === 'someOtherEvent') { | |
//action for that event | |
} else if (eventData?.type === 'open-share-modal') { | |
//example of how to open a share modal | |
//do this in your frontend | |
// window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'open-share-modal', data: { message: `some message: \n${window.location.href}` } })); | |
const shareData = eventData?.data; | |
RNshare.share({ | |
message: shareData?.message, | |
url: shareData?.url, | |
title: shareData?.title, | |
}); | |
} else if (eventData?.type === 'open-external-link') { | |
setUrl(eventData?.url); | |
} else if (eventData?.type === 'open-internal-link') { | |
//example of how to navigate to a different screen | |
//do this in your frontend | |
// window.ReactNativeWebView.postMessage(JSON.stringify({ data:{screen:"screenName",params:{screen:"nestedScreenName",params:{paramKey:paramValue}}},type:"open-internal-link" })); | |
navigation.navigate( | |
eventData?.data?.screen, | |
eventData?.data?.params, | |
); | |
} else if (eventData?.type === 'download') { | |
//example of how to download a file | |
//do this in your frontend | |
//window.ReactNativeWebView.postMessage(JSON.stringify({type:"download",url:url, name:"filename",nameWithExtension:"fileName.pdf",mimeType:"application/pdf"})) | |
if (Platform.OS === 'ios') { | |
try { | |
let dirs = ReactNativeBlobUtil.fs.dirs; | |
ReactNativeBlobUtil.config({ | |
fileCache: true, | |
path: | |
dirs.DocumentDir + | |
`/${eventData.nameWithExtension}`, | |
}) | |
.fetch('GET', eventData?.url) | |
.then(async (res) => { | |
await Share.open({ | |
url: res.path(), | |
saveToFiles: true, | |
filename: eventData?.name, | |
type: eventData?.mimeType, | |
}); | |
await ReactNativeBlobUtil.fs | |
.unlink(res.path()) | |
.catch((err) => { | |
console.log(err); | |
}); | |
}) | |
.catch((err) => { | |
console.log(err); | |
}); | |
} catch (err) { | |
console.log(err); | |
} | |
} else { | |
try { | |
ReactNativeBlobUtil.config({ | |
fileCache: true, | |
}) | |
.fetch('GET', eventData?.url) | |
.then(async (res) => { | |
await ReactNativeBlobUtil.MediaCollection.copyToMediaStore( | |
{ | |
name: eventData?.name, | |
parentFolder: '', | |
mimeType: eventData?.mimeType, | |
}, | |
'Download', | |
res.path(), | |
); | |
await ReactNativeBlobUtil.fs | |
.unlink(res.path()) | |
.catch((err) => { | |
console.log(err); | |
}); | |
setShowToast(true); | |
setTimeout(() => { | |
setShowToast(false); | |
}, 2000); | |
}) | |
.catch((err) => { | |
console.log(err); | |
}); | |
} catch (err) { | |
console.log(err); | |
} | |
} | |
} else if (eventData?.type === 'share') { | |
//example of how to share a file | |
//do this in your frontend | |
//window.ReactNativeWebView.postMessage(JSON.stringify({type:"share",url:url, name:"filename",nameWithExtension:"filename.pdf",mimeType:"application/pdf"})) | |
try { | |
let dirs = ReactNativeBlobUtil.fs.dirs; | |
ReactNativeBlobUtil.config({ | |
fileCache: true, | |
path: dirs.DocumentDir + `/${eventData.nameWithExtension}`, | |
}) | |
.fetch('GET', eventData?.url) | |
.then(async (res) => { | |
await Share.open({ | |
url: | |
Platform.OS === 'ios' | |
? res.path() | |
: `file://${res.path()}`, | |
filename: eventData?.name, | |
type: eventData?.mimeType, | |
}); | |
await ReactNativeBlobUtil.fs | |
.unlink(res.path()) | |
.catch((err) => { | |
console.log(err); | |
}); | |
}) | |
.catch((err) => { | |
console.log(err); | |
}); | |
} catch (err) { | |
console.log(err); | |
} | |
} else if (eventData?.type === 'download-base64') { | |
//example of how to download a base64 file | |
//do this in your frontend | |
// window.ReactNativeWebView.postMessage(JSON.stringify({type: 'download-base64', url: url, name: 'fileName',nameWithExtension: 'fileName.pdf',mimeType: 'application/pdf'})); | |
if (Platform.OS === 'ios') { | |
try { | |
Share.open({ | |
url: `data:${eventData.mimeType};base64${eventData?.url}`, | |
saveToFiles: true, | |
filename: eventData?.name, | |
type: eventData?.mimeType, | |
}); | |
} catch (err) { | |
console.log(err); | |
} | |
} else { | |
try { | |
const path = | |
ReactNativeBlobUtil.fs.dirs.DownloadDir + | |
`/${eventData.nameWithExtension}`; | |
ReactNativeBlobUtil.fs | |
.writeFile(path, eventData?.url, 'base64') | |
.then(async () => { | |
await ReactNativeBlobUtil.MediaCollection.copyToMediaStore( | |
{ | |
name: eventData?.name, | |
parentFolder: '', | |
mimeType: eventData?.mimeType, | |
}, | |
'Download', | |
path, | |
); | |
setShowToast(true); | |
setTimeout(() => { | |
setShowToast(false); | |
}, 2000); | |
}); | |
ReactNativeBlobUtil.fs.unlink(path).catch((err) => { | |
console.log(err); | |
}); | |
} catch (err) { | |
console.log(err); | |
} | |
} | |
} | |
}; | |
useEffect(() => { | |
if (isFocused) { | |
const checkInternet = async () => { | |
try { | |
const res = await axios.get('https://google.com'); | |
if (res.status === 200) { | |
setShowNetworkModal(false); | |
} else { | |
setShowNetworkModal(true); | |
} | |
} catch (err) { | |
console.log('err', err); | |
setShowNetworkModal(true); | |
} | |
}; | |
checkInternet(); | |
} | |
}, [isFocused]); | |
const onBackPress = () => { | |
if (canGoBackRef.current) { | |
if (currentUrlRef.current === url) { | |
return false; | |
} else { | |
webviewRef.current.goBack(); | |
return true; | |
} | |
} | |
}; | |
useFocusEffect( | |
useCallback(() => { | |
const subscription = BackHandler.addEventListener( | |
'hardwareBackPress', | |
onBackPress, | |
); | |
navigation.setOptions({ | |
headerShown: true, | |
header: () => ( | |
//header compoenent must have a back button due to ios back limitations | |
<HeaderComponentWithBackButton onPress={() => onBackPress()} showBack={true} /> | |
), | |
}); | |
return () => subscription.remove(); | |
}, [currentUrlRef.current, url]), | |
); | |
return ( | |
<View style={{flex: 1, backgroundColor: '#fff'}}> | |
<NetworkErrorModalWithRetryButton | |
showModal={showNetworkModal} | |
setShowModal={setShowNetworkModal} | |
RetryFetch={() => { | |
setShowNetworkModal(false); | |
setWebviewKey((prev) => !prev); | |
}} | |
/> | |
{showNetworkModal && ( | |
<LoaderContainer> | |
<WarningIcon /> | |
</LoaderContainer> | |
)} | |
{showToast && ( | |
<Toast> | |
<Text>File successfully saved to downloads</Text> | |
</Toast> | |
)} | |
{!showNetworkModal && ( | |
<WebView | |
ref={webviewRef} | |
source={{ | |
uri: url, | |
}} | |
startInLoadingState={true} | |
renderLoading={() => ( | |
<LoaderContainer> | |
<Loader /> | |
</LoaderContainer> | |
)} | |
allowsBackForwardNavigationGestures={true} | |
allowsInlineMediaPlayback={true} | |
mediaPlaybackRequiresUserAction={false} | |
onError={() => { | |
setShowNetworkModal(true); | |
}} | |
key={webviewKey} | |
style={{flex: 1}} | |
onMessage={onMessage} | |
onNavigationStateChange={(navState) => { | |
canGoBackRef.current = navState.canGoBack; | |
currentUrlRef.current = navState.url; | |
}} | |
onContentProcessDidTerminate={() => { | |
setWebviewKey((prev) => !prev); | |
}} | |
onRenderProcessGone={() => { | |
setWebviewKey((prev) => !prev); | |
}} | |
injectedJavaScript={injectedJs} | |
/> | |
)} | |
</View> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment