React Native WebView pre-render for faster performance - how to do it? - webview

In React Native, when using the WebView component, it starts to load the external content at the moment when the component will be rendered.
To increase performance in the application, I have tried to pre-fetch the external HTML so that it is ready when the component will be rendered. It seems like it is only an actual call to the render method will cause the loading to start and this is only controlled by what is rendered on the screen. I suppose React Native has no concept of shadow DOM that could be used to call the render method a head of time.
Trying to manipulate the lifecycle methods does also not work and is probably not a correct way of doing it either?
I have also tried to do a fetch() of the external HTML-content, with the right user-agent in the header, and pass the responseText to the WebComponent. This sometimes works for some sites of sources, but for others i run into ACAP (Automated Content Access Protocol) issues, to this is not the preferred solution.
Is there a way to pre-fetch external HTML content to a WebView component so that it displays faster?

fetch method runs on react side, fetch keep cache but that available for react apis and there component. WebView has there own caching concept. It's a browser. Caching of fetch will not avaialble for WebView. For faster loading by pre loaded data, You should fetch data by WebView instance of fetch api.
You can create a hidden WebView by setting width and height 0 and load your site on that. This will load your site on ViewView and keep cache, that will available for next time loading.
Here is a sample
import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,
View,
WebView,
Alert,
ActivityIndicator,
} from 'react-native';
// const url = 'https://github.com/facebook/react-native'
// const url = 'https://in.yahoo.com/?p=us'
const url = 'https://www.facebook.com/'
class TestWebView extends Component {
render() {
var renderTime = Date.now();
return (
<WebView
source={{uri: url}}
style={{marginTop: 20, flex: 1}}
onLoad={() => {
Alert.alert('On load event', `Loading time : ${Date.now() - renderTime}`)
}}
/>
);
}
}
export default class App extends Component<{}> {
constructor(props) {
super(props)
this.state = {
isLoaded: false,
}
}
render() {
if (this.state.isLoaded) {
return (
<TestWebView />
)
}
return (
<View style={styles.container}>
<View style={{height: 0, width: 0}}>
<WebView
source={{uri: url}}
onLoad={() => {
this.setState({isLoaded: true})
}}
/>
</View>
<ActivityIndicator />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
I test this. After first time loaded data on WebView, It reduce 70% loading on actual WebView where we want to show to user.

Related

Prevent FlatList re-rendering causing performance problems

I'm building a React Native App using Hooks and ES6.
The Home screen fetches some data from API and display it as image galleries.
The Home screen is the parent component which includes a slideshow gallery and a Flatlist as children.
1) Slideshow - image gallery autoplay that run some operations (fetching URL in case user clicks on them) on each image iteration (every 3 secs) - This causes the re-render on the entire parent components and its children but it renders every 3 secs as supposed
2) Flatlist - just a simple Flatlist that takes an array of images - this should only rendered once when the parent component is initially loaded first
The problem
Every 3 seconds a new image is displayed in the slideshow, a function runs to fetch some props for the image displayed, the parent component is re-rendered but there is no need for Flatlist in the second gallery to run again since the array of images is the same as initially loaded (not changed)
This is my Parent code
const getTopTenMovies = (state) => state.moviescreen.popularmovies //fetching data from Redux
const upcomingMovies = (state) => state.moviescreen.upcomingmovies
const MoviesScreen = (props) => {
const topTenMovies = useSelector(getTopTenMovies);
const [imageIndex, setImageIndex] = useState("");
/* useEffect below runs every time imageIndex changes (a new image is displayed every 3 secs) */
useEffect(() => {
setCarouselMovieId(selectedImageItem.id);
setCarouselMovieTitle(selectedImageItem.title);
}, [imageIndex]);
/* user clicks on an image and is taken to another screen
const fetchMovieHandler = async (movieId, movieTitle) => {
props.navigation.navigate({
routeName: "MovieDetail",
params: {
assetId: movieId,
assetName: movieTitle,
},
});
};
return (
<ImageGallery
data={topTenMovies}
currentImage=setImageIndex(index)
...
/>
<View style={styles.containerRow}>
<FlatList
horizontal={true}
data={upcomingMovies}
initialNumToRender={5}
windowSize={10}
removeClippedSubviews={true}
keyExtractor={(item) => item.id.toString()}
renderItem={(itemData) => (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
onAssetSelection={fetchMovieHandler}
/>
)}
/>
</View>
</View>
)
}
The component renders the individual images within the Flatlist and passes the parameters (movieId, movieTitle) to the fetchMovieHandler function.
HomeItem code below...
class HomeItem extends React.PureComponent {
render() {
const assetHandler = async () => {
this.props.onAssetSelection(this.props.id, this.props.title);
};
console.log("HomeItem is called");
return (
<TouchableOpacity onPress={assetHandler}>
<View>
<Image
key={this.props.id}
source={{
uri: `https://*******${this.props.poster}`,
}}
/>
</View>
</TouchableOpacity>
);
}
}
export default React.memo(HomeItem);
Every 3 seconds the HomeItem is called and I see 10 log entries (one for each image and render - upcomingMovies has 10 images). From the UI everything looks fines since the images dont seems to change probably because I've defined the HomeItem as PureComponent and using React.memo(HomeItem) but it is causing performance issues since I'm getting the following error:
VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc
I tried to include the Flatlist inside the HomeItem component but the problem persists.
Following further research and thanks to #landorid suggestion I figured out that I need control the renderItem of the FlatList by declaring it in a separate function with a useCallback hook and dependency on my data source.
Tried to include Flatlist in a separate PureComponent my the re-rendering kept happening.
The ideal solution is to change this code
renderItem={(itemData) => (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
onAssetSelection={fetchMovieHandler}
/>
)}
to something like this
.....
const renderRow = ({ itemData }) => {
return (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
title={itemData.item.title}
onAssetSelection={fetchMovieHandler}
/>
);
};
keyExtractor = (item) => item.id.toString();
return (
<FlatList
horizontal={true}
data={upcomingMovies}
initialNumToRender={5}
windowSize={10}
removeClippedSubviews={true}
keyExtractor={keyExtractor}
renderItem={renderRow}
/>
and then wrapping the renderRow() function in a useCallback hook.
The problem is that my data source data={upcomingMovies} is an array of objects with 2 levels and I need to use itemData.item.title to retrieve the title value for example.
While it works in my current renderItem settings it won't work when I use it in an external function as in
const renderRow = ({ itemData }) => {
return (
<HomeItem
id={itemData.item.id}
poster={itemData.item.poster_path}
title={itemData.item.title}
onAssetSelection={fetchMovieHandler}
/>
);
};
I simply get, "item" variable not available
Any suggestion on how to modify the function?
If I were you I would change the following:
Don't place inline functions in FlatList (keyExtractor, renderItem)! Place it to separated function.
Use simple Component extension instead of PureComponent and compare your props with `shouldComponentUpdate. (I think the passed function called onAssetSelection causes the re-render. Check it.)
You don't need to use React.memo because it is for functional components.

How to set height of React Native Webview? [duplicate]

Hi I know it's a known issue about the auto height of webview in react native,
and I have tried all the possibles solutions I've found on the internet such as :
https://gist.github.com/epeli/10c77c1710dd137a1335
https://github.com/danrigsby/react-native-web-container/blob/master/index.js
and all the solutions suggested in:
React native: Is it possible to have the height of a html content in a webview?
But unfortunately none of these seems to work for me,
I understand that the workaround they all suggest is to set the title to the height, but in my case it seems that the title always stays the same which is :
"text/html ...." and the rest of my html.
I get the html content from an API, it comes without a body, head or html tags, I've also tried adding these tags manually to the html and nothing seems to work.
I would love to hear if anyone else had that problem and how did it get fixed.
I wrap WebView inside a View, and set the height from the View.
<View style={{ height: 200 }}>
<WebView
automaticallyAdjustContentInsets={false}
source={{uri: 'https://player.vimeo.com/video/24156534?title=0&byline=0&portrait=0'}}
/>
</View>
I just follow this guide: https://github.com/react-native-community/react-native-webview/blob/master/docs/Guide.md#communicating-between-js-and-native and succeeded in my work. Here is solution:
1. Define script to send document height to native env after loaded website.
2. Handle onMesssage of webview component and reset Height via state.
const webViewScript = `
setTimeout(function() {
window.postMessage(document.documentElement.scrollHeight);
}, 500);
true; // note: this is required, or you'll sometimes get silent failures
`;
...
constructor(props) {
super(props);
this.state = {
webheight:100,
}
...
<WebView style={{height: this.state.webheight}}
automaticallyAdjustContentInsets={false}
scrollEnabled={false}
source={{uri: "http://<your url>"}}
onMessage={event => {
this.setState({webheight: parseInt(event.nativeEvent.data)});
}}
javaScriptEnabled={true}
injectedJavaScript ={webViewScript}
domStorageEnabled={true}
></WebView>
Hope that help!
A reliable implementation of this behavior is with useAutoheight hook from #formidable-webview/webshell library.
The latter allows to inject "features" into WebViews, e.g. scripts and behaviors.
In this example, we will use 3 features + the aforementioned hook:
HandleHTMLDimensionsFeature which is required by useAutoheight hook to get document size updates;
ForceResponsiveViewportFeature to work around mobile virtual viewport;
ForceElementSizeFeature to work around cyclic size constraints
This component should work with any webpage.
import React from 'react';
import makeWebshell, {
HandleHTMLDimensionsFeature,
ForceResponsiveViewportFeature,
ForceElementSizeFeature,
useAutoheight
} from '#formidable-webview/webshell';
import WebView from 'react-native-webview';
const Webshell = makeWebshell(
WebView,
new HandleHTMLDimensionsFeature(),
new ForceResponsiveViewportFeature({ maxScale: 1 }),
new ForceElementSizeFeature({
target: 'body',
heightValue: 'auto',
widthValue: 'auto'
})
);
export default function ResilientAutoheightWebView(props) {
const { autoheightWebshellProps } = useAutoheight({
webshellProps: props
});
return <Webshell {...autoheightWebshellProps} />;
}
More resources:
Try this on Expo
Full guide here.
Using postMessage and onMessage like below worked for me perfectly.
Credit to iamdhj
onWebViewMessage = (event: WebViewMessageEvent) => {
this.setState({webViewHeight: Number(event.nativeEvent.data)})
}
render() {
return (
<ScrollView>
<WebView
style={{ height: this.state.webViewHeight }}
source={{html: '...'}}
onMessage={this.onWebViewMessage}
injectedJavaScript='window.ReactNativeWebView.postMessage(document.body.scrollHeight)'
/>
</ScrollView>
)
}
The WebView has default styles. If you want to set height, you also need to add flex: 0, as stated in the documentation:
Please note that there are default styles (example: you need to add flex: 0 to the style if you want to use height property).
I made a little component to make this functionality reusable if it helps anyone!
import React, { useState } from "react";
import WebView from "react-native-webview";
const DynamicHeightWebView = (props) => {
const [height, setHeight] = useState(0);
const webViewScript = `
setTimeout(function() {
window.ReactNativeWebView.postMessage(document.documentElement.scrollHeight);
}, 500);
true; // note: this is required, or you'll sometimes get silent failures
`;
return <WebView
{...props}
style={{
...props.style,
height: height,
}}
automaticallyAdjustContentInsets={false}
scrollEnabled={false}
onMessage={event => {
setHeight(parseInt(event.nativeEvent.data));
}}
javaScriptEnabled={true}
injectedJavaScript ={webViewScript}
domStorageEnabled={true}
useWebKit={true}
/>
}
export default DynamicHeightWebView;
Apparently the problem was I had javaScriptEnabled={false}.
After enabling it everything worked.
I waste whole day to fix the height issue but in the end I had to shift to another library
This one is easy and good
https://github.com/archriss/react-native-render-html
You can get the content height by injecting the JS code as suggested by #ken-ratanachai-s. Although, You will experience certain irregularities in some devices (Extra height after the content). This is becuase the javascript returns the content height in pixels, but we need to use display points in react native. To fix this, Divide the height from javascript with the pixel ratio as follows.
import { WebView, PixelRatio } from 'react-native'
const [webviewHeight, setWebviewHeight] = useState(0)
const onProductDetailsWebViewMessage = event => {
setWebviewHeight(Number(event.nativeEvent.data)/PixelRatio.get())
}
return <WebView
originWhitelist={['*']}
style={{ height: productDetailsWebviewHeight }}
onMessage={onProductDetailsWebViewMessage}
injectedJavaScript='window.ReactNativeWebView.postMessage(document.body.scrollHeight)'
source={{ html: "..." }}
/>
Pixel ratio ref.: https://reactnative.dev/docs/pixelratio
Courtesy: https://stackoverflow.com/a/65976827/5321660
use package react-native-autoheight-webview
I recommend react-native-autoheight-webview.
it perfect work for me.
https://github.com/iou90/react-native-autoheight-webview
UPDATE:
Best answer is #Ken Ratanachai S.'s answer.
https://stackoverflow.com/a/65976827/9757656

How to prevent layout from overlapping with iOS status bar

I am working on tutorial for React Native navigation. I found out that all layout starts loading from top of screen instead of below of the status bar. This causes most layouts to overlap with the status bar. I can fix this by adding a padding to the view when loading them. Is this the actual way to do it? I don' think manually adding padding is an actual way to solve it. Is there a more elegant way to fix this?
import React, { Component } from 'react';
import { View, Text, Navigator } from 'react-native';
export default class MyScene extends Component {
static get defaultProps() {
return {
title : 'MyScene'
};
}
render() {
return (
<View style={{padding: 20}}> //padding to prevent overlap
<Text>Hi! My name is {this.props.title}.</Text>
</View>
)
}
}
Below shows the screenshots before and after the padding is added.
Now you can use SafeAreaView which is included in React Navigation:
<SafeAreaView>
... your content ...
</SafeAreaView>
There is a very simple way to fix this. Make a component.
You can create a StatusBar component and call it first after the first view wrapper in your parent components.
Here is the code for the one I use:
'use strict'
import React, {Component} from 'react';
import {View, Text, StyleSheet, Platform} from 'react-native';
class StatusBarBackground extends Component{
render(){
return(
<View style={[styles.statusBarBackground, this.props.style || {}]}> //This part is just so you can change the color of the status bar from the parents by passing it as a prop
</View>
);
}
}
const styles = StyleSheet.create({
statusBarBackground: {
height: (Platform.OS === 'ios') ? 18 : 0, //this is just to test if the platform is iOS to give it a height of 18, else, no height (Android apps have their own status bar)
backgroundColor: "white",
}
})
module.exports= StatusBarBackground
After doing this and exporting it to your main component, call it like this:
import StatusBarBackground from './YourPath/StatusBarBackground'
export default class MyScene extends Component {
render(){
return(
<View>
<StatusBarBackground style={{backgroundColor:'midnightblue'}}/>
</View>
)
}
}
I tried a more simple way for this.
We can get the height of Status Bar on android and use SafeAreaView along with it to make the code work on both platforms.
import { SafeAreaView, StatusBar, Platform } from 'react-native';
If we log out Platform.OS and StatusBar.currentHeight we get the logs,
console.log('Height on: ', Platform.OS, StatusBar.currentHeight);
Height on: android 24 and
Height on: android 24
We can now optionally add margin/padding to our container view using
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0
The final code in App.js is below:
export default class App extends React.Component {
render() {
return (
<SafeAreaView style={{ flex: 1, backgroundColor: "#fff" }}>
<View style={styles.container}>
<Text>Hello World</Text>
</View>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0
}
});
#philipheinser solution does work indeed.
However, I would expect that React Native's StatusBar component will handle that for us.
It doesn't, unfortunately, but we can abstract that away quite easily by creating our own component around it:
./StatusBar.js
import React from 'react';
import { View, StatusBar, Platform } from 'react-native';
// here, we add the spacing for iOS
// and pass the rest of the props to React Native's StatusBar
export default function (props) {
const height = (Platform.OS === 'ios') ? 20 : 0;
const { backgroundColor } = props;
return (
<View style={{ height, backgroundColor }}>
<StatusBar { ...props } />
</View>
);
}
./index.js
import React from 'react';
import { View } from 'react-native';
import StatusBar from './StatusBar';
export default function App () {
return (
<View>
<StatusBar backgroundColor="#2EBD6B" barStyle="light-content" />
{ /* rest of our app */ }
</View>
)
}
Before:
After:
The react-navigation docs have a great solution for this. First off, they recommend not to use the SafeAreaView included with React Native because:
While React Native exports a SafeAreaView component, it has some
inherent issues, i.e. if a screen containing safe area is animating,
it causes jumpy behavior. In addition, this component only supports
iOS 10+ with no support for older iOS versions or Android. We
recommend to use the react-native-safe-area-context library to handle
safe areas in a more reliable way.
Instead, they recommend react-native-safe-area-context - with which it would look like this:
import React, { Component } from 'react';
import { View, Text, Navigator } from 'react-native';
import { useSafeArea } from 'react-native-safe-area-context';
export default function MyScene({title = 'MyScene'}) {
const insets = useSafeArea();
return (
<View style={{paddingTop: insets.top}}>
<Text>Hi! My name is {title}.</Text>
</View>
)
}
I would like to note that it's probably a better idea to use the SafeAreaView that this library offers though, since phones these days may also have elements at the bottom that can overlap UI elements. It all depends on your app of course. (For more detail on that, see the react-navigation docs I linked to in the beginning.)
Here is a way that works for iOS:
<View style={{height: 20, backgroundColor: 'white', marginTop: -20, zIndex: 2}}>
<StatusBar barStyle="dark-content"/></View>
You can handle this by adding a padding to you navigation bar component or just ad a view that has the same hight as the statusbar at the top of your view tree with a backgroundcolor like the facebook app does this.
Just Simple User React native Default StatusBar to achieve this funcationality.
<View style={styles.container}>
<StatusBar backgroundColor={Color.TRANSPARENT} translucent={true} />
<MapView
provider={PROVIDER_GOOGLE} // remove if not using Google Maps
style={styles.map}
region={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.015,
longitudeDelta: 0.0121,
}}
/>
</View>
If you combine SaveAreaView and StatusBar, you get it.
https://reactnative.dev/docs/statusbar
https://reactnative.dev/docs/safeareaview
Just do this:
<SafeAreaView>
<View style={{flex: 1}}>
<StatusBar translucent={false} backgroundColor="#fff" />
// Your dark magic here
</View>
</SafeAreaView>
[This answer is applicable to Android emulators]
Hi, I have imported status bar from "react-native" and called it at the end of block with status bar style set to auto and it worked for me, the code below is for reference:
import { SafeAreaView,Button, StyleSheet, Text, TextInput, View } from 'react-native';
import { StatusBar } from 'react-native';
export default function App() {
return (
<SafeAreaView style={styles.appContainer}>
<View >
<TextInput placeholder='Add your course goal' />
<Button title="Add Goals" />
</View>
<View>
<Text>List of goals..</Text>
</View>
<StatusBar style="auto" />
</SafeAreaView>
);
}

React Native custom component doesn't set component's size dynamically

I'm trying to build an RN component that displays Tweets. I forked a great RN component, react-native-fabric-twitterkit and added a component that displays a tweet. So far so good but when I want to display that Tweet inside a ScrollView I have to specify the components height and width otherwise it won't get displayed. Any idea what am I doing wrong?
Here is my forked RN component: https://github.com/adamivancza/react-native-fabric-twitterkit
And here is a test application that showcases my issue: https://github.com/adamivancza/react-native-twitterkit-test
<View style={styles.container}>
<ScrollView
style={{ flex: 1 }}>
// this component is displayed because I've set a specific height
<Tweet
style={{ flex: 1, height: 200 }}
tweetId='788105828461006848'/>
// this component is not displayed
<Tweet
style={{ flex: 1 }}
tweetId='788105828461006848'/>
</ScrollView>
</View>
The github issue linked by #smilledge has the answer.
When adding views dynamically in a native component it seems you need to manually trigger a layout cycle in order for react-native to pick up on the changes. (perhaps related to react native updating it's shadow-view)
#Override
public void requestLayout() {
super.requestLayout();
post(measureAndLayout);
}
private final Runnable measureAndLayout = new Runnable() {
#Override
public void run() {
measure(
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
}
};

React native: webview height

Hi I know it's a known issue about the auto height of webview in react native,
and I have tried all the possibles solutions I've found on the internet such as :
https://gist.github.com/epeli/10c77c1710dd137a1335
https://github.com/danrigsby/react-native-web-container/blob/master/index.js
and all the solutions suggested in:
React native: Is it possible to have the height of a html content in a webview?
But unfortunately none of these seems to work for me,
I understand that the workaround they all suggest is to set the title to the height, but in my case it seems that the title always stays the same which is :
"text/html ...." and the rest of my html.
I get the html content from an API, it comes without a body, head or html tags, I've also tried adding these tags manually to the html and nothing seems to work.
I would love to hear if anyone else had that problem and how did it get fixed.
I wrap WebView inside a View, and set the height from the View.
<View style={{ height: 200 }}>
<WebView
automaticallyAdjustContentInsets={false}
source={{uri: 'https://player.vimeo.com/video/24156534?title=0&byline=0&portrait=0'}}
/>
</View>
I just follow this guide: https://github.com/react-native-community/react-native-webview/blob/master/docs/Guide.md#communicating-between-js-and-native and succeeded in my work. Here is solution:
1. Define script to send document height to native env after loaded website.
2. Handle onMesssage of webview component and reset Height via state.
const webViewScript = `
setTimeout(function() {
window.postMessage(document.documentElement.scrollHeight);
}, 500);
true; // note: this is required, or you'll sometimes get silent failures
`;
...
constructor(props) {
super(props);
this.state = {
webheight:100,
}
...
<WebView style={{height: this.state.webheight}}
automaticallyAdjustContentInsets={false}
scrollEnabled={false}
source={{uri: "http://<your url>"}}
onMessage={event => {
this.setState({webheight: parseInt(event.nativeEvent.data)});
}}
javaScriptEnabled={true}
injectedJavaScript ={webViewScript}
domStorageEnabled={true}
></WebView>
Hope that help!
A reliable implementation of this behavior is with useAutoheight hook from #formidable-webview/webshell library.
The latter allows to inject "features" into WebViews, e.g. scripts and behaviors.
In this example, we will use 3 features + the aforementioned hook:
HandleHTMLDimensionsFeature which is required by useAutoheight hook to get document size updates;
ForceResponsiveViewportFeature to work around mobile virtual viewport;
ForceElementSizeFeature to work around cyclic size constraints
This component should work with any webpage.
import React from 'react';
import makeWebshell, {
HandleHTMLDimensionsFeature,
ForceResponsiveViewportFeature,
ForceElementSizeFeature,
useAutoheight
} from '#formidable-webview/webshell';
import WebView from 'react-native-webview';
const Webshell = makeWebshell(
WebView,
new HandleHTMLDimensionsFeature(),
new ForceResponsiveViewportFeature({ maxScale: 1 }),
new ForceElementSizeFeature({
target: 'body',
heightValue: 'auto',
widthValue: 'auto'
})
);
export default function ResilientAutoheightWebView(props) {
const { autoheightWebshellProps } = useAutoheight({
webshellProps: props
});
return <Webshell {...autoheightWebshellProps} />;
}
More resources:
Try this on Expo
Full guide here.
Using postMessage and onMessage like below worked for me perfectly.
Credit to iamdhj
onWebViewMessage = (event: WebViewMessageEvent) => {
this.setState({webViewHeight: Number(event.nativeEvent.data)})
}
render() {
return (
<ScrollView>
<WebView
style={{ height: this.state.webViewHeight }}
source={{html: '...'}}
onMessage={this.onWebViewMessage}
injectedJavaScript='window.ReactNativeWebView.postMessage(document.body.scrollHeight)'
/>
</ScrollView>
)
}
The WebView has default styles. If you want to set height, you also need to add flex: 0, as stated in the documentation:
Please note that there are default styles (example: you need to add flex: 0 to the style if you want to use height property).
I made a little component to make this functionality reusable if it helps anyone!
import React, { useState } from "react";
import WebView from "react-native-webview";
const DynamicHeightWebView = (props) => {
const [height, setHeight] = useState(0);
const webViewScript = `
setTimeout(function() {
window.ReactNativeWebView.postMessage(document.documentElement.scrollHeight);
}, 500);
true; // note: this is required, or you'll sometimes get silent failures
`;
return <WebView
{...props}
style={{
...props.style,
height: height,
}}
automaticallyAdjustContentInsets={false}
scrollEnabled={false}
onMessage={event => {
setHeight(parseInt(event.nativeEvent.data));
}}
javaScriptEnabled={true}
injectedJavaScript ={webViewScript}
domStorageEnabled={true}
useWebKit={true}
/>
}
export default DynamicHeightWebView;
Apparently the problem was I had javaScriptEnabled={false}.
After enabling it everything worked.
I waste whole day to fix the height issue but in the end I had to shift to another library
This one is easy and good
https://github.com/archriss/react-native-render-html
You can get the content height by injecting the JS code as suggested by #ken-ratanachai-s. Although, You will experience certain irregularities in some devices (Extra height after the content). This is becuase the javascript returns the content height in pixels, but we need to use display points in react native. To fix this, Divide the height from javascript with the pixel ratio as follows.
import { WebView, PixelRatio } from 'react-native'
const [webviewHeight, setWebviewHeight] = useState(0)
const onProductDetailsWebViewMessage = event => {
setWebviewHeight(Number(event.nativeEvent.data)/PixelRatio.get())
}
return <WebView
originWhitelist={['*']}
style={{ height: productDetailsWebviewHeight }}
onMessage={onProductDetailsWebViewMessage}
injectedJavaScript='window.ReactNativeWebView.postMessage(document.body.scrollHeight)'
source={{ html: "..." }}
/>
Pixel ratio ref.: https://reactnative.dev/docs/pixelratio
Courtesy: https://stackoverflow.com/a/65976827/5321660
use package react-native-autoheight-webview
I recommend react-native-autoheight-webview.
it perfect work for me.
https://github.com/iou90/react-native-autoheight-webview
UPDATE:
Best answer is #Ken Ratanachai S.'s answer.
https://stackoverflow.com/a/65976827/9757656

Resources