react-native webview loading indicator - react-native-ios

I am trying to show loading indicator in webweb as follow. Loading indicator is showing but there is white background show after page is loaded. If I change to startInLoadingState to false, web content is showing but loading indicator does not show. It is happing in "react-native": "0.46.3" on ios
renderLoadingView() {
return (
<ActivityIndicator
animating = {this.state.visible}
color = '#bc2b78'
size = "large"
style = {styles.activityIndicator}
hidesWhenStopped={true}
/>
);
}
<WebView
source={source}
renderLoading={this.renderLoadingView} startInLoadingState={true} />

I like this approach which shows the activity indicator overlayed on the loading Webview so you don't have to wait until the entire page is loaded to start seeing content.
constructor(props) {
super(props);
this.state = { visible: true };
}
hideSpinner() {
this.setState({ visible: false });
}
render() {
return (
<View style={{ flex: 1 }}>
<WebView
onLoad={() => this.hideSpinner()}
style={{ flex: 1 }}
source={{ uri: this.props.navigation.state.params.url }}
/>
{this.state.visible && (
<ActivityIndicator
style={{ position: "absolute", top: height / 2, left: width / 2 }}
size="large"
/>
)}
</View>
);
}

A nice approach is setting the property startInLoadingState to true and set the renderLoading to return the desired View.
See the example below.
displaySpinner() {
return (
<View>
{/* Your spinner code goes here.
This one commes from react-native-material-kit library */}
<SingleColorSpinner />
</View>
);
}
render() {
return (
<WebView
startInLoadingState={true}
source={{ uri: this.state.myUri }}
renderLoading={() => {
return this.displaySpinner();
}}
/>
);
}

I have steped on that problem and after some research i found a pretty good solution.
It requires the "react-native-loading-spinner-overlay"
npm install --save react-native-loading-spinner-overlay
index.android.js
import Spinner from 'react-native-loading-spinner-overlay';
const main = 'http://www.myURI.pt';
class MyApp extends Component {
constructor(props) {
super(props);
this.state = { uri: main, visible: true };
}
showSpinner() {
console.log('Show Spinner');
this.setState({ visible: true });
}
hideSpinner() {
console.log('Hide Spinner');
this.setState({ visible: false });
}
render() {
return (
<View>
<Spinner
visible={this.state.visible}
textContent={'Loading...'}
textStyle={{ color: '#FFF' }}
/>
<WebView
scalesPageToFit
source={{ uri: this.state.uri }}
onLoadStart={() => (this.showSpinner())}
onLoad={() => (this.hideSpinner())}
/>
</View>
);
}
}
I think i didn't miss any line.

Alter your renderLoadingView function to the following, and the loading indicator should work as desired:
renderLoadingView() {
return (
<ActivityIndicator
color='#bc2b78'
size='large'
styles={styles.activityIndicator}
/>
);
}
So essentially, just remove the animating (as it is not required for the given usage) and hidesWhenStopped props from your ActivityIndicator. Hope this helps.

Copy & Pasteable: Minimal Webview Component with Loading Indicator
import React, { Component } from "react";
import { ActivityIndicator} from "react-native";
import { WebView } from "react-native-webview";
// Pass a "uri" prop as the webpage to be rendered
class WebViewScreen extends Component {
constructor(props) {
super(props);
this.state = { visible: true };
}
hideSpinner() {
this.setState({ visible: false });
}
render() {
return (
<React.Fragment>
<WebView
onLoadStart={() => this.setState({ visible: true })}
onLoadEnd={() => this.setState({ visible: false })}
// Pass uri in while navigating with react-navigation. To reach this screen use:
// this.props.navigation.navigate("WebViewScreen", {uri: "google.ca"});
source={{ uri: this.props.navigation.state.params.uri }}
/>
{this.state.visible ? (
<ActivityIndicator
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
jusityContent: "space-around",
flexWrap: "wrap",
alignContent: "center",
}}
size="large"
/>
) : null}
</React.Fragment>
);
}
}
export default WebViewScreen;

react-native webview is now deprecated.
You can import react-native-webview and do the following:
<WebView
source={{ uri: 'https://reactnative.dev' }}
startInLoadingState={true}
renderLoading={() => <Loading />}
/>

If you want to show a Spinner and then replace that spinner with the WebView already loaded, this is your answer:
import React from 'react';
import { StyleSheet, ActivityIndicator, View } from 'react-native';
import { WebView } from "react-native-webview";
function MyApp() {
const Spinner = () => (
<View style={styles.activityContainer}>
<ActivityIndicator size="large" color={white} />
</View>
);
return (
<WebView
bounces={false}
startInLoadingState={true}
renderLoading={Spinner}
style={styles.container}
source={{ uri: yourURL }}
showsHorizontalScrollIndicator={false}
scalesPageToFit
/>
)
}
export default StyleSheet.create({
container: {
flex: 1
},
activityContainer: {
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
top: 0,
left: 0,
backgroundColor: black,
height: '100%',
width: '100%'
}
});

I've used #AdamG's solution but there have a problem with absolute path. The below solution is set ActivityIndicator to the center but with a different way.
<View style={{ flex: 1 }}>
<WebView
onLoad={() => this.hideSpinner()}
style={{ flex: 1 }}
source={{ uri: 'yourhtml.html' }}
/>
<View style={{backgroundColor:'white', height:1}}></View>
{this.state.visible && (
<View style={{flex:1, alignItems:'center'}}>
<ActivityIndicator
size="large"
/>
</View>
)}
</View>
There is 2 another {flex:1} View and ActivityIndicator is in top of the bottom View. I've centered that.
<View style={{backgroundColor:'white', height:1}}></View>
And this line is set the opacity when you have loading state, there have two different View. In top view there is WebView and there is a black bottom border View belong to the WebView.For closing I've patched it with a white helper view.

Hey bro this is my solution, you have to use the event onLoadEnd instead onLoad, the event onLoad is not working for me.
import React, { Component } from 'react';
import { StyleSheet, ActivityIndicator, View } from 'react-native';
import { WebView } from "react-native-webview";
export default class MainActivity extends Component {
constructor(props) {
super(props);
this.state = { visible: true };
}
showSpinner() {
console.log('Show Spinner');
this.setState({ visible: true });
}
hideSpinner() {
console.log('Hide Spinner');
this.setState({ visible: false });
}
render() {
return (
<View
style={this.state.visible === true ? styles.stylOld : styles.styleNew}>
{this.state.visible ? (
<ActivityIndicator
color="#009688"
size="large"
style={styles.ActivityIndicatorStyle}
/>
) : null}
<WebView
style={styles.WebViewStyle}
//Loading URL
source={{ uri: 'https://aboutreact.com' }}
//Enable Javascript support
javaScriptEnabled={true}
//For the Cache
domStorageEnabled={true}
//View to show while loading the webpage
//Want to show the view or not
//startInLoadingState={true}
onLoadStart={() => this.showSpinner()}
onLoad={() => this.hideSpinner()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
stylOld: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
styleNew: {
flex: 1,
},
WebViewStyle: {
justifyContent: 'center',
alignItems: 'center',
flex: 1,
marginTop: 40,
},
ActivityIndicatorStyle: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
},
});

My response will not directly answer your question but it will get you to think about using Skeleton loaders in place of traditional loading indicators (ie. spinner). I've replaced all my project's indicators with Skeletons and have received great user feedback since the change.
Here's a detailed explanation:
https://mechanicalrock.github.io/2022/07/11/replace-circular-loaders-with-skeletons-a.html
Hope this plants the seed as a pattern going forward.

<WebView
style={{
flex: 1
}}
startInLoadingState={true}
source={{ uri: "https://google.com" }}
renderLoading={() => (
)}
/>

Related

TouchableOpacity doesn't respond

I'm very new to React native and lately just been messing around with it. I've been having problems every time I use one of the touchable components. For some reason it doesn't respond when I click the touchable component. I tried looking at other solutions but got no where. (I'm also using Expo if you need to know that)
import {
ImageBackground,
StyleSheet,
View,
SafeAreaView,
Platform,
StatusBar,
Text,
TouchableOpacity,
} from "react-native";
const StartScreen = () => {
return (
<ImageBackground
source={require("../assets/Background.png")}
style={{ flex: 1 }}
>
<SafeAreaView style={styles.ContentArea}>
<TouchableOpacity onPress={console.log("Button pressed!")}>
<View style={styles.nextButton}>
<Text style={{ fontWeight: "bold", fontSize: 15 }}>Test</Text>
</View>
</TouchableOpacity>
</SafeAreaView>
</ImageBackground>
);
};
const styles = StyleSheet.create({
ContentArea: {
flex: 1,
paddingTop: Platform.OS === "android" ? StatusBar.currentHeight : 0,
justifyContent: "flex-end",
alignItems: "center",
},
nextButton: {
width: 150,
height: 40,
backgroundColor: "tomato",
marginBottom: 45,
alignItems: "center",
justifyContent: "center",
borderRadius: 50,
},
});
export default StartScreen;
You are passing a function invocation inside the onPress method. This makes the console.log run on the render. console.log() returns undefined, so your onPress prop is ultimately undefined itself. What you want to do, is pass a reference to a function, or an anonymous function instead. Here's an example:
...
<TouchableOpacity onPress={() => console.log("Button pressed!")}>
<View style={styles.nextButton}>
<Text style={{ fontWeight: "bold", fontSize: 15 }}>Test</Text>
</View>
</TouchableOpacity>
...

Uncaught Error: MUI: makeStyles is no longer exported from

I started getting this error after migrating to react version 18
makeStyles.js:3 Uncaught Error: MUI: makeStyles is no longer exported from #mui/material/styles. You have to import it from #mui/styles.
I have looked through the docs and tried different things but it didn't work.
How do I make this work?
here is my code:
import { ThemeProvider, createTheme } from "#mui/material/styles";
import { makeStyles } from "#mui/styles";
export default function Program() {
const theme = useTheme();
const [programData, setProgramData] = useState([]);
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
flexWrap: "wrap",
justifyContent: "space-around",
overflow: "hidden",
backgroundColor: theme.palette.background.paper,
},
image: {
height: "72%",
width: "530px",
objectFit: "cover",
paddingBottom: 3,
},
}));
useEffect(() => {
axios
.get("https://cryptic-shelf-72177.herokuapp.com/programs/api")
.then((response) => {
setProgramData([...response.data]);
})
.catch(function (error) {
console.log(error);
});
}, []);
const matches = useMediaQuery(theme.breakpoints.down("xs"));
const classes = useStyles();
return (
<div className={classes.root} style={{ padding: "3vw" }}>
<ImageListItem key="Subheader" style={{ height: "auto" }}></ImageListItem>
<ImageList
rowHeight={550}
cols={matches ? 1 : 3}
className={classes.gridList}
gap={12}
style={{ background: "#A52A2A " }}
>
{programData.length > 0 &&
programData.map((tile, index) => {
return (
<ImageListItem
key={Math.floor(Math.random() * new Date().getTime())}
component={Link}
to={"/programs/" + tile._id + "/programcomments"}
style={{ textDecoration: "none", color: "black" }}
>
<img
src={tile.programImage}
alt={tile.title}
class={classes.image}
/>
<ImageListItemBar
titlePosition="top"
title={tile.title}
// style={{ height: 400 }}
/>
<Typography
paragraph
style={{
borderBottom: "2px solid",
background: "white",
padding: 7,
}}
>
{tile.description.substring(0, 222)}..
</Typography>
</ImageListItem>
);
})}
</ImageList>
</div>
);
}
Everything was working fine until i migrated to React version 18, and made some material us changes that comes with React version 18 and materialui 5
Try this:
Remove all dependencies by deleting node-modules folder in your project.
Open a new terminal and run 'npm install'.
(Those two instructions above are optional)
Since there has been a major change moving from Mui v4 to v5, makeStyles() is deprecated, so that you have to use styled() function instead, and import it from '#emotion/styled';
Example:
import styled from '#emotion/styled';
const useStyles = styled(theme => ({
root: {
//Your styling rules
}
}))
Take a look at the following link as well, thanks for #TeslaDelMar 's answer:
https://www.reddit.com/r/reactjs/comments/uabs6g/material_ui_react_18/
Good luck!

flex box is not working for <Image> in react native android

I just started learning React-Native. I am facing issue while trying to render image on to the screen, it is not appearing. I have tried so many solutions from Google and stack overflow but could not able to solve the issue.
Can someone please help me. Below are the details.
I am using RN 0.53.0 for Android with atom editor. Please let me know if you need more details.
This is where I am extracting data: http://rallycoding.herokuapp.com/api/music_albums
import React from 'react';
import { Text, View, Image, Linking } from 'react-native';
import Card from './Card';
import CardSection from './CardSection';
import Button from './Button';
const AlbumDetail = ({ album }) => {
const {
title,
artist,
thumbnail_image,
image,
url
} = album;
const {
headerContentStyle,
thumbnailStyle,
thumbnailContainerStyle,
imageStyle
} = styles;
return (
<Card>
<CardSection>
<View style={thumbnailContainerStyle}>
<Image style={thumbnailStyle} source={{ uri: thumbnail_image }} />
</View>
<View style={headerContentStyle}>
<Text style={{ fontSize: 18 }}>{title}</Text>
<Text>{artist}</Text>
</View>
</CardSection>
<CardSection>
<View>
<Image style={imageStyle} source={{ uri: image }} />
</View>
</CardSection>
<CardSection>
<Button onPress={() => Linking.openURL(url)}>
Purchase
</Button>
</CardSection>
</Card>
);
};
const styles = {
headerContentStyle: {
flexDirection: 'column',
justifyContent: 'space-around'
},
thumbnailStyle: {
height: 50,
width: 50
},
thumbnailContainerStyle: {
justifyContent: 'center',
alignItems: 'center',
marginLeft: 10,
marginRight: 10
},
imageStyle: {
height: 350,
width: null,
flex: 1,
justifyContent: 'center'
}
};
export default AlbumDetail;
Here is your code :
Please remove View tag from the image
<CardSection>
<Image style={imageStyle} source={{ uri: image }} />
</CardSection>
stylesheet : for imageStyle
imageStyle: {
height: 300,
width: null,
flex: 1,
}
Please check it and let me know if it working or not.
Here:
imageStyle: {
height: 350,
width: null,
flex: 1,
justifyContent: 'center'
}
You have set the image width to null. Try setting it to a reasonable value.
You need add the style to Image with height and width.
in your code you are not adding the style, try this:
<Image style={styles.imageStyle} source={{ uri: image }} />
Add too a valid size for height and width when you are using a image from internet.

react-native nested text with differnt fontsizes (initial, first letter)

I currently write a news reader app in react-native and want to style the first letter of an article as an initial, like below:
For my first attempt I use the nested text aproach. The code is attached below. And this is my current result:
The code in the render function:
<View style={styles.container}>
<Text style={styles.text}>
<Text style={styles.initial}>W</Text>
<Text>
elcome to React Native! To get started, edit index.android.js To get started, edit index.android.js To get started, edit index.android.js
</Text>
</Text>
</View>
My stylesheet:
const fontSize = 14;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
initial: {
fontSize: fontSize * 2,
},
text: {
fontSize: fontSize,
color: '#333333',
},
});
My Question:
How can I style my first char to get a pretty initial?
Environment
react: 16.0.0-alpha.6
react-native: 0.44.2
Android 6.0 on an emulated Nexus 5
you can try in this way too, add the regular style for parent Text element and required style for the "W" text child element.
export default class DemoProject extends Component {
render() {
return (
<View style={styles.container}>
*<Text>
<Text style={[styles.textCommon, styles.firstLetter]}>W</Text>
elcome</Text>*
</View>
);
}
}
text: {
fontSize: fontSize,
color: '#333333',
}
This is one way of doing it:
export default class DemoProject extends Component {
render() {
return (
<View style={styles.container}>
<Text style={[styles.textCommon, styles.firstLetter]}>W</Text>
<Text>elcome</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
flexDirection: 'row'
},
textCommon: {
color: '#333333',
},
firstLetter: {
fontSize: 23,
}
});
I think this is the best solution for your question
How can I make the first letter push itself into the paragraph like a newspaper style?
just convert css to react-style

React Native - Sticky footer at the bottom of container

I'm trying to make a <View> called footer stick at the bottom of the right container.
Here is a live example:
https://rnplay.org/apps/G3rHqQ
If I make the left container higher than the right one then it won't work. If the right container is higher than the left one then it works....
The red and orange elements are dynamic, have different height depending on their content. The blue one, instead, should always stick to the bottom of the right container.
I have also tried with position: 'absolute'; bottom:0; left: 0; right: 0; and it does stick to the bottom but only IF the right container is higher than the left one.
It looks like you need to set a flex:1 on the outermost container to get the flex properties working the way you want. I've set up a working project here and pasted the code below as well.
https://rnplay.org/apps/WoceXg
'use strict';
import React, { AppRegistry, StyleSheet, View, Text } from 'react-native';
const SampleApp = React.createClass({
render: function() {
return (
<View style={{ flex:1 }}>
<View style={styles.wrapper}>
<View style={styles.left}>
<Text>Left</Text>
<Text>Left</Text>
<Text>Left</Text>
<Text>Left</Text>
<Text>Left</Text>
<Text>Left</Text>
<Text>Left</Text>
<Text>Left</Text>
<Text>Left</Text>
</View>
<View style={styles.right}>
<View style={styles.rightInner}>
<View style={styles.content}>
<Text>content</Text>
<Text>content</Text>
</View>
<View style={styles.footer}>
<Text>Sticky</Text>
</View>
</View>
</View>
</View>
<View style={{ flex:1 }}></View>
</View>
);
},
});
const styles = StyleSheet.create({
wrapper: {
flexDirection: 'row',
paddingTop: 50,
flex:1
},
left: {
backgroundColor: 'lightblue',
flex: 1,
},
right: {
backgroundColor: 'lightgreen',
flex: 4,
},
rightInner: {
flex: 1,
backgroundColor: 'orange',
},
content: {
flex: 1,
},
footer: {
backgroundColor: 'green',
}
});
AppRegistry.registerComponent('SampleApp', () => SampleApp);
I'm trying to do something similar. I need a View to stick to the bottom. I used poistion: 'absolute', bottom:0 and it does stick to the bottom but the width of the view does not stretch anymore.

Resources