Why don't WebView script injections work? - react-native

this document suggests that I should be able to inject values into a web page displayed by the WebView component such that the value can be used by loaded the page:
https://github.com/react-native-community/react-native-webview/blob/master/docs/Guide.md#communicating-between-js-and-native
specifically, the code below shows how to set a value within the window object but does not show how it is used:
import React, { Component } from 'react';
import { View } from 'react-native';
import { WebView } from 'react-native-webview';
export default class App extends Component {
render() {
const runFirst = `
window.isNativeApp = true;
true; // note: this is required, or you'll sometimes get silent failures
`;
return (
<View style={{ flex: 1 }}>
<WebView
source={{uri: 'my-url-here'}}
injectedJavaScriptBeforeContentLoaded={runFirst}
/>
</View>
);
}
}
the page I'm loading looks like this:
<html>
<body>
<script>
alert(window.isNativeApp)
</script>
</body>
</html>
which displays undefined. I've also tried:
<html>
<body>
<script>
var fn = function() {
alert(window.isNativeApp)
}
document.addEventListener('DOMContentLoaded', fn, false)
</script>
</body>
</html>
with identical results. given that I'm supposed to be able to send the webpage values, how am I supposed to use them?
from my package.json:
"react-dom": "~16.9.0",
"react-native": "^0.62.1",
"react-native-webview": "^8.2.1",
Appendix I
in fact, the above doesn't seem to run at all. if I try the following:
const runFirst = `
console.log('INJECTION')
alert('INJECTION')
true; // note: this is required, or you'll sometimes get silent failures
`;
I get neither an alert, nor a trace in the log. of course, I'm not sure whether alert() can work before the document is loaded, or whether the log would be visible to me in the regular app's console output
by contrast injectedJavaScript does seem to run, albeit after the document loads, which means that at the time that the <script> in my doc runs, the value hasn't yet been set

for the next poor sod that struggles with this, the library is broken and will (someday) be fixed, but in the meantime, this works:
<WebView
source={{uri: 'my-url-here'}}
injectedJavaScriptBeforeContentLoaded={runFirst}
onMessage={event => { alert(event.nativeEvent.data )}}
/>
the onMessage is intended for communications in the other direction but its mere presence makes the code work

Related

How to render HTML in react-native

I am getting the html response from the api as shown below. I want to render the text and open the url on click of the text that is given in the response.
"Details": "<span style=\"font-family: Georgia; font-size: 24px;\"><em><span style=\"color: #0070c0;\">Click here to view News.</span></em></span>"
You can use WebView as HTML renderer like this,
import React, { Component } from 'react';
import { WebView } from 'react-native-webview';
class MyInlineWeb extends Component {
render() {
return (
<WebView
originWhitelist={['*']}
source={{ html: '<h1>Hello world</h1>' }}
/>
);
}
}
See official docs here
use this package react-native-render-html
extremely customizable and easy to use and aims at being able to render anything you throw at it.
import React, { Component } from 'react';
import { ScrollView, Dimensions } from 'react-native';
import HTML from 'react-native-render-html';
const htmlContent = `
<h1>This HTML snippet is now rendered with native components !</h1>
<h2>Enjoy a webview-free and blazing fast application</h2>
<img src="https://i.imgur.com/dHLmxfO.jpg?2" />
<em style="textAlign: center;">Look at how happy this native cat is</em>
`;
export default class Demo extends Component {
render () {
return (
<ScrollView style={{ flex: 1 }}>
<HTML html={htmlContent} imagesMaxWidth={Dimensions.get('window').width} />
</ScrollView>
);
}
}

How to open external link in browser from inside Webview in React Native?

I have below webview component, I wanted the link to be opened in a mobile browser app rather than in inside current webview in my app.
return() {
<WebView source={{ html: `Google`}} />
}
This worked for me.
import { WebView, Linking, NavState } from 'react-native';
const html = `
Google
Twitter
`
class WebViewWrapper extends Component {
private webview;
handleNavigationStateChange = (event: NavState) => {
if (event.url) {
this.webview.stopLoading();
Linking.openURL(event.url);
}
};
render() {
return (
<WebView
originWhitelist={['*']}
ref={ref => {
this.webview = ref;
}}
source={{ html }}
onNavigationStateChange={this.handleNavigationStateChange}
/>
);
}
}
you can do this:
import { Linking } from 'react-native';
on you launch function
Linking.openURL( some_url );
For more details, follow this full example: Just what you want
Calling stopLoading freezes the WebView and links may stop working after pressing them once. Better to use onShouldStartLoadWithRequest (API Reference).
So the code could look something like this:
import { Linking } from 'react-native';
import { WebView } from 'react-native-webview';
const html = `
Google
Twitter
`;
const shouldStartLoadWithRequest = (req) => {
// open the link in native browser
Linking.openURL(req.url);
// returning false prevents WebView to navigate to new URL
return false;
};
const MyComponent = () => (
<WebView
originWhitelist={['*']}
source={{ html }}
onShouldStartLoadWithRequest={shouldStartLoadWithRequest}
/>
);
export default MyComponent;
Note that onShouldStartLoadWithRequest behaves a bit differently on iOS.
Try this one. This will save your time and patience :)
import HTML from 'react-native-render-html';
<HTML
html={this.state.content.description}
// imagesMaxWidth={Dimensions.get('window').width}
onLinkPress={(evt, href) => { Linking.openURL(href) }}
/>
Fully supportive of android, ios and Expo too. And has some cool customisations you can try with.
https://www.npmjs.com/package/react-native-render-html

Injecting Custom JavaScript Into React Native's Webview

I want to inject javascript codes to injectedJavaScript method of react native web view. I could inject one javascript code working fine but multiple can't. Is there any trick to do that?
injectedJavaScript={`document.querySelector('.header-wrapper').style.display='none' ` }
this works.
But i want something like this to inject multiple javasript things but not work.
let jsCode = `(
function() {
document.querySelector('.footer').style.display='none' ;
document.querySelector('.tabs').style.display='none' ;
document.querySelector('.header-wrapper').style.display='none' ;
document.querySelector('.wrapper').style.margin-top=-70px ;
})();`;
render() {
return (
<WebView
source={{uri: 'blabla.com'}}
style={{marginTop: 20}}
injectJavaScript={jsCode}
javaScriptEnabledAndroid={true}
/>
);
}
When i try this i am getting unexpected token etc. How could i inject multiple javascripts to react native web view ? Thanks in advance.
Full code :
import React from 'react';
import { StyleSheet, Text, View,WebView } from 'react-native';
export default class App extends React.Component {
render() {
return (
<WebView
source={{uri: 'https://trends.google.com/trends/trendingsearches/daily?geo=TR'}}
injectedJavaScript={`document.querySelector('.trending-searches-footer').style.display='none';`
+ ` document.querySelector('.content-header-buttons daily-header-buttons').style.display='none'; `
+ ` document.querySelector('.trending-feed-tabs').style.display='none'; `
+ ` document.querySelector('.header-wrapper').style.display='none'; `
+ ` document.querySelector('.trending-feed-page-wrapper').style.marginTop='-70px'; `
}
javaScriptEnabled={true}
ref="WEBVIEW_REF"
/>
);
}
}
You have an error in your javascript, this would cause the script to fail:
document.querySelector('.wrapper').style.margin-top=-70px; // this is wrong syntax
try this
document.querySelector('.wrapper').style.marginTop='-70px';
To use it with injectedJavascript write your script as you would if it was one line, it doesn't make any difference, but you should make sure that your javascript doesn't have any errors:
let jsCode = `
document.querySelector('.footer').style.display='none';
document.querySelector('.tabs').style.display='none';
document.querySelector('.header-wrapper').style.display='none';
document.querySelector('.wrapper').style.marginTop='-70px';
`;
And the use injectedJavaScript and not injectJavaScript to use it.
<WebView
source={{uri: 'blabla.com'}}
style={{marginTop: 20}}
injectedJavaScript={jsCode}
javaScriptEnabledAndroid={true}
/>

React Native - Interactive initial page

I'm interested in having a view which initially loads with my React Native app that essentially has nested components in it. These components will give visual queues to the user as to what state the app is in, eg: still loading data from the server, etc. Basically, it's not just a static splash screen. I might also add some kind of spinner/progress bar, eg: other animated components.
There are solutions out there for static splash screens that initially show while your app loads into memory, but I need to be able to load an initial component, and then remove it when the application's data is ready to go. Is there a convention/pattern to follow in order to achieve this? Is there a way to mount a component, then remove it when it's no longer necessary allowing the rest of the app to be displayed? What's a best practice for this using React Native?
This is what I used to do:
Use <Modal /> to provide your initial, interactive page. It will blocks the screen, with semi-transparent background; If you like it to be full width, just use flex: 1 within the <View /> inside <Modal />.
Use global object / queue for loading status information. My choice is rxjs, then your initial page can just listen to this one source of truth, suggest a BehaviorSubject. So you can subscribe on it for something like:
...
{ tag: 'FetchRemoteData', progress: 10 }
{ tag: 'LoadingComponent', progress: 5 }
{ tag: 'FetchRemoteData', progress: 20 }
...
Read it until match your "load complete" conditions, then close the model.
To make it clear with code.
app.js
render() {
return (
<View>
<InitialBlockingPage />
<YourMainApp />
</View>
);
}
initial-blocking-page.js
constructor(props) {
super(props);
this.state = {
visible: true
};
}
componentDidMount() {
globalQueue.subscribe( () => {
/// pseudo code: until fully loaded
if (fullloaded) this.setState({visible: false});
});
}
render() {
return (
<Modal visible={this.state.visible}>
<SplashScreenWithData />
</Modal>
);
}

How to create and connect a custom component theme for Native Base

I'm using Native Base 2.0+, the themes are ejected and using StyleProvider I am able to tweak and customize any Native Base component according to my theme, no problem.
Following the docs, it's stated that to add themes to a custom component we should define a namespace for said component and merge it with the instantiated styling as well. Code example below:
import React, { Component } from 'react'
import { Header, Left, Body, Right, Button, Title, Text, connectStyle } from 'native-base'
import Colors from '../Themes/Colors'
import ApplicationStyles from '../Themes/ApplicationStyles'
class NBHeader extends Component {
render () {
const styles = this.props.style
return (
<Header style={styles.header}>
<Left>
<Button transparent>
<Text style={styles.headerBackButton}>
{'< Back'}
</Text>
</Button>
</Left>
<Body>
<Title>Login</Title>
</Body>
<Right />
</Header>
)
}
}
export default connectStyle('CustomComponents.Header', ApplicationStyles)(NBHeader)
In this case, namespace for the component is 'CustomComponents.Header'. Then, we use StyleProvider to connect the Theme:
import React, { Component } from 'react';
import { StyleProvider } from 'native-base';
class CustomComponent extends Component {
render() {
return (
// connect styles to props.style defined by the theme
const styles = this.props.style;
<StyleProvider style={customTheme}>
Your Custom Components
</StyleProvider>
);
}
}
// Define your own Custom theme
const customTheme = {
'yourTheme.CustomComponent': {
// overrides CustomComponent style...
}
};
Since I've ejected the theme, I entered the new namespace for the Custom Component in NB's Theme file, so it should already be added and cascaded using StyleProvider.
So for instance, if I want the header to be 'red' and have a padding of '10' due to theming rules, I add those as default props of 'CustomComponents.Header' and forget about it, it will always be applied to the component as long as the StyleProvider is cascading themes.
The problem is I cannot get this defined Custom Component's default theme to be applied. I don't know if it's a problem with my code or how Native Base works. Any help would be appreciated and I can provide further code if needed.