Open a WebView after a button pressed with reactNative - react-native

I am developing a mobile app with React Native. I want to open a WebView after a button pressed. Here is my code, but its not working. The button onPress method is not working.
import React, { Component } from 'react';
import { View, StyleSheet, Button, WebView } from 'react-native';
import { Constants } from 'expo';
export default class webView extends Component {
onNavigationStateChange = navState => {
if (navState.url.indexOf('https://www.google.com') === 0) {
const regex = /#access_token=(.+)/;
const accessToken = navState.url.match(regex)[1];
console.log(accessToken);
}
};
renderContent() {
return (
<WebView
source={{
uri: '',
}}
onNavigationStateChange={this.onNavigationStateChange}
startInLoadingState
scalesPageToFit
javaScriptEnabled
style={{ flex: 1 }}
/>
);
}
render() {
return (
<View style={styles.container}>
<Button
style={styles.paragraph}
title="Login"
onPress={() => this.renderContent()}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
});
I tried onPress={this.renderContent()} this also, but it give an exception. What can I do ?

You aren't rendering your WebView in the the render() method of the Component. Just think of render as your DOM of the webpage. You need to provide the component a place in the render component, than you can remove it or add it, see i am calling the renderContent from the render method. So whenever the state variable showWebView is true it will render the WebView You should do something like this:
import React, { Component } from 'react';
import { View, StyleSheet, Button, WebView } from 'react-native';
import { Constants } from 'expo';
export default class webView extends Component {
state={
showWebView: false
}
onNavigationStateChange = navState => {
if (navState.url.indexOf('https://www.google.com') === 0) {
const regex = /#access_token=(.+)/;
const accessToken = navState.url.match(regex)[1];
console.log(accessToken);
}
};
renderContent() {
return (
<WebView
source={{
uri: '',
}}
onNavigationStateChange={this.onNavigationStateChange}
startInLoadingState
scalesPageToFit
javaScriptEnabled
style={{ flex: 1 }}
/>
);
}
render() {
return (
<View style={styles.container}>
{ this.state.showWebView && this.renderContent() }
<Button
style={styles.paragraph}
title="Login"
onPress={() => this.setState({showWebView: true})}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
},
});

I think you must import the following
import { WebView } from 'react-native-webview'
instead of
'react'

Related

I need to fix bug with react native. what is the problem?

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
import React, { PureComponent, useState } from 'react'
import { StoryContainer } from 'react-native-stories-view'
import {
TouchableOpacity,
Alert,
StyleSheet,
View,
Text,
SafeAreaView,
ImageBackground,
Image,
Platform,
StatusBar,
} from 'react-native'
import { connect } from 'react-redux'
class StoryViewScreen extends PureComponent {
constructor(props) {
super(props);
this.state = {
}
}
render() {
const { files } = this.props.route.params;
const fileUrls = [];
for (var i = 0; i < files.length; i++) {
fileUrls.push(files[i].uri);
}
console.log("files path:", fileUrls);
return (
<View style={{ flex: 1, flexDirection: 'column' }}>
{Platform.OS === 'ios' && (
<View style={{
backgroundColor: 'gray',
height: 45,
}}>
<StatusBar barStyle="light-content" backgroundColor={'green'} />
</View>
)}
{Platform.OS === 'android' && (
<StatusBar barStyle="dark-content" backgroundColor={'white'} />
)}
<SafeAreaView style={{ flex: 1, flexDirection: 'column', backgroundColor: 'gray' }}>
<StoryContainer
visible={true}
enableProgress={false}
images={fileUrls}
duration={5}
containerStyle={{
width: '100%',
height: '100%',
}} />
{/* <Text>This is teh realdksfjdsklfj</Text> */}
</SafeAreaView>
</View>
);
}
};
const style = StyleSheet.create({
});
function mapStateToProps(state) {
return {
// currentUser: state.user.currentUser,
};
}
function mapDispatchToProps(dispatch) {
return {
dispatch
};
}
export default connect(mapStateToProps, mapDispatchToProps)(StoryViewScreen);
You cannot use useState in class component, instead use setState to set the state.

How to write Custom flash message in react native

I want to display a custom message after successful record update when boolean value of recordUpdateSuccess become true and after 3seconds it should disappear.
{recordUpdateSuccess ? this.renderRecordUpdatedSucess() : null}
I have function to display message:
renderRecordUpdatedSucess = () => (
<View style={styles.sucessAlert}>
<Text style={styles.sucessAlert}>Record updated successfully.</Text>
</View>
)
I tried to use setTimeout() to display message but not working.
Any idea to acheive this one.
I don't want to use Toast, any third party library for this one.
Custom flash message (No external Library)
Working Example: https://snack.expo.io/#msbot01/1dcddc
import * as React from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import Constants from 'expo-constants';
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
flashMessage: false
}
}
onPress(){
this.setState({
flashMessage: true
},()=>{setTimeout(() => this.closeFlashMessage(), 3000)})
}
closeFlashMessage(){
this.setState({
flashMessage: false
})
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={()=>{this.onPress()}}>
<Text>Click Me</Text>
</TouchableOpacity >
{this.state.flashMessage==true?
<View style={styles.flashMessage}>
<Text style={{color:'white'}}>This is custom Flash message</Text>
</View>
:
null
}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
flashMessage:{
position:'absolute',
backgroundColor:'green',
width:'100%',
justifyContent:'center',
alignItems:'center',
height:40,
top:0
}
});

How to show loading progress or spinner in the middle of the screen with React Native?

I am developing React Native app.
I was able to solve all problems by myself but this is exception.
I am going to load another screen with bottom tab navigator.
For example, after user login to the app, it should show main home screen which has many pictures and many style sheet effects, icons. Because of that, after login confirm ( I mean after alert of the login confirm), the main home screen appears after a few seconds.
So I want to show some spinner in the login screen while loading main home screen in the background and when it is ready to show, erase spinner and show main home screen.
How can I do this?
My bottom tab navigator was simply created with createBottomTabNavigator() method.
So in your case you can do several things
You can use React Native Activity Indicator -> View
You can use Overlay Library -> react-native-loading-spinner-overlay -> View GitHub
If you like to make loading like facebook / instagram -> then use react-native-easy-content-loader -> View GitHub
Assume that you are using React Native Activity Indicator :
import { ActivityIndicator } from "react-native";
export default class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true
};
}
//Get Home Screen Data API Action
componentDidMount() {
this.loadAPI(); // Call home screen get data API function
}
//Login API Function
loadAPI = () => {
this.setState({ isLoading: true }); // Once You Call the API Action loading will be true
fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json"
}
})
.then(response => response.json())
.then(responseText => {
// You can do anything accroding to your API response
this.setState({ isLoading: false }); // After getting response make loading to false
})
.catch(error => {});
};
render() {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
{this.state.isLoading && <ActivityIndicator color={"#fff"} />}
</View>
);
}
}
If you want to hide all the view until loading finish like images, so you can use custom library instead of Activity Indicator.
I have created my custom Loader component. Using this you can display built in ActivityIndicator or your custom gif loader image with overlay.
Loader.js
import React, { Component } from 'react';
import {
StyleSheet,
View,
Modal,
Image,
ActivityIndicator
} from 'react-native';
class Loader extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: this.props.isLoading
}
}
static getDerivedStateFromProps(nextProps) {
return {
isLoading: nextProps.isLoading
};
}
render() {
return (
<Modal
transparent={true}
animationType={'none'}
visible={this.state.isLoading}
style={{ zIndex: 1100 }}
onRequestClose={() => { }}>
<View style={styles.modalBackground}>
<View style={styles.activityIndicatorWrapper}>
<ActivityIndicator animating={this.state.isLoading} color="black" />
{/* If you want to image set source here */}
{/* <Image
source={require('../assets/images/loader.gif')}
style={{ height: 80, width: 80 }}
resizeMode="contain"
resizeMethod="resize"
/> */}
</View>
</View>
</Modal>
)
}
}
const styles = StyleSheet.create({
modalBackground: {
flex: 1,
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'space-around',
backgroundColor: '#rgba(0, 0, 0, 0.5)',
zIndex: 1000
},
activityIndicatorWrapper: {
backgroundColor: '#FFFFFF',
height: 100,
width: 100,
borderRadius: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-around'
}
});
export default Loader
Now you can use it when you have to display loading indicator as below :
<Loader isLoading={this.state.isLoading} />
import { ActivityIndicator } from 'react-native';
export default class LoginScreen extends Component {
constructor(props) {
super(props);
this.state = {
spinner : true
}
}
render() {
return (
<View style={{flex : 1, justifyContent: 'center', alignItems: 'center',}}>
{
this.state.spinner &&
<ActivityIndicator color={'#fff'} />
}
</View>
)
}
}
So you can show the SPinner for suppose when you have to load an API or something and when you get the response of api, you can set spinner loading value to false.
For eg :
import {View, ActivityIndicator } from 'react-native';
export default class MainScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
spinner : true
}
}
componentDidMount(){
this.loadApi();
}
loadApi = async() => {
let result = axios.get('url').then((data) =>{
this.setState({spinner:false});
}
).catch((err) => this.setState({spinner:false})
}
render() {
return (
<View style={{flex : 1, justifyContent: 'center', alignItems: 'center',}}>
{
this.state.spinner? <ActivityIndicator color={'#fff'} />:<View><Text>Data loaded</Text></View>
}
</View>
)
}
}
you have to use ActivityIndicator you can have to load this activityindicator before getting data from the server , you have to check below code hope you will understand
import React, {useEffect, useState} from 'react';
import {ActivityIndicator, View, Dimensions} from 'react-native';
import HomeScreen from './Home';
const DataViewer = () => {
const [data, setData] = useState([]);
const {height, width} = Dimensions.get('window');
useEffect(() => {
fetch('http://example.com/movies.json')
.then(response => {
return response.json();
})
.then(myJson => {
setData(myJson);
});
});
return data.length > 0 ? (
<HomeScreen data={data} />
) : (
<View
style={{justifyContent: 'center', alignItems: 'center', height, width}}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
);
};
export default DataViewer;
You can use the Activity indicator as the default loading animation. But you can also use Lottie files to implement custom loading screen animation on your project by installing npm i lottie-react-native or yarn add lottie-react-native

Force unmounting on screen change

I recently integrated React Redux and Redux Thunk into my application in the hope that it would better allow me to manage state across screens.
However, using my navigation library (react native router flux), when ever I navigate between screens I get warnings of trying to set state across unmounted components and I am not sure what I would even need to unmount in componentWillUnmount as no calls should happen after a screen navigation.
My question then is, how can I force unmount everything on componentWillUnmount? Is there something built into React Native that I should use? Or, in my navigation library?
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import * as Font from 'expo-font'
class CustomText extends Component {
async componentDidMount() {
await Font.loadAsync({
'varelaround-regular': require('../../../assets/fonts/varelaround-regular.ttf'),
'opensans-regular': require('../../../assets/fonts/opensans-regular.ttf'),
'opensans-bold': require('../../../assets/fonts/opensans-bold.ttf'),
});
this.setState({ fontLoaded: true });
}
state = {
fontLoaded: false,
};
setFontType = type => {
switch (type) {
case 'header':
return 'varelaround-regular';
case 'bold':
return 'opensans-bold';
default:
return 'opensans-regular';
}
};
render() {
const font = this.setFontType(this.props.type ? this.props.type : 'normal');
const style = [{ fontFamily: font }, this.props.style || {}];
const allProps = Object.assign({}, this.props, { style: style });
return (
<View>
{
this.state.fontLoaded ? (
<Text {...allProps}>{this.props.children}</Text>
) : <Text></Text>
}
</View>
);
}
}
export default CustomText;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
And one of my screens:
import React from "react";
import { ActivityIndicator, Image, StyleSheet, View } from "react-native";
import { Actions } from "react-native-router-flux";
import { connect } from "react-redux";
import * as profile from "../actions/profile";
import {
CustomText
} from "../components/common/";
class Home extends React.Component {
componentDidMount() {
this.props.loadProfile();
}
renderScreen() {
return (
<View style={{ flex: 1 }}>
<View style={{ flex: 0.3 }}>
<CustomText type="header" style={styles.headerTextStyle} onPress={() => Actions.home()}>
Hello {this.props.name}!
</CustomText>
</View>
</View>
);
}
renderWaiting() {
return (
<GradientBackground type="purple">
<View
style={{ flex: 1, justifyContent: "center", alignItems: "center" }}
>
<ActivityIndicator size="large" color="#FFF" />
</View>
</GradientBackground>
);
}
render() {
return (
<View style={{ flex: 1 }}>
{this.props.isLoading == true
? this.renderWaiting()
: this.renderScreen()}
</View>
);
}
}
function mapStateToProps(state) {
return {
name: state.profile.profile.friendly_name,
isLoading: state.profile.isLoading,
error: state.profile.error
};
}
function mapDispatchToProps(dispatch) {
return {
loadProfile: () => dispatch(profile.loadProfile())
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Home);
const styles = StyleSheet.create({
headerTextStyle: {
color: "#FFFFFF",
fontSize: 40,
textAlign: "center",
marginVertical: 50
},
basicViewStyle: {
flex: 1
}
});

Two of my common components that I'm importing with index.js don't fire

I'm doing this course at Udemy. Files: https://github.com/StephenGrider/ReactNativeReduxCasts/tree/master/auth
The issue I'm having is with importing common components. It's only for 2 of the common components--the rest work fine. Card, CardSection, Header, Input.
When I try to import the Button or Spinner, they won't fire. But if I use the basic functionality (putting the TouchableOpacity or ActivityIndicator in the file directly and do all the styling THERE), they work fine.
Here's the file structure:
Here's /components/common/index.js
export * from './Header';
export * from './Input';
export * from './Card';
export * from './CardSection';
export * from './Button';
export * from './Spinner';
Here's Button.js
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
const Button = ({ propOnPress, children }) => {
const { buttonStyle, textStyle } = styles;
return (
<TouchableOpacity onPress={propOnPress} style={buttonStyle}>
<Text style={textStyle}>
{children}
</Text>
</TouchableOpacity>
)
}
const styles = {
textStyle: {
alignSelf: 'center',
color: '#fff',//'#007aff',
fontSize: 16,
fontWeight: '600',
paddingTop: 10,
paddingBottom: 10
},
buttonStyle: {
flex: 1,
alignSelf: 'stretch',
backgroundColor: '#007aff', //'#fff',
borderRadius: 5,
borderWidth: 1,
borderColor: '#007aff',
marginLeft: 5,
marginRight: 5,
}
}
export { Button };
Here's Spinner.js
import React from 'react';
import { View, ActivityIndicator } from 'react-native';
const Spinner = ({ size }) => {
return (
<View style={styles.spinnerStyle}>
<ActivityIndicator size={size || 'large'} />
</View>
)
}
const styles = {
spinnerStyle: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
}
export { Spinner }
Here's where I import them in LoginForm.js
import { Card, CardSection, Button, Spinner, Input } from './common';
And where they're used in the code in LoginForm.js
renderButton() {
//console.log('render button');
if (this.state.loading) {
console.log('returning the spinner');
return <Spinner animating={this.state.loading} size="small" />;
}
console.log('gonna return a button');
return(
<Button onPress={this.onYouPressedIt.bind(this)}>Log in</Button>
);
}
What am I doing wrong?
Issues I see with the Button.js file
Property onPress doesn't exist. You created a property called propOnPress
So, your Button component should be used like this
<Button propOnPress={}>...</Button>
Issues I see with the Spinner.js file
Property animating doesn't exist on the component. The only properties you created is size.
Solution would be to simply add an animating property to your Spinner component.
Component would end up looking like this
const Spinner = ({ animating, size }) => {
return (
{
animating ? (
<View style={styles.spinnerStyle}>
<ActivityIndicator size={size || 'large'} />
</View>
) : null
}
)
}
I assume the animating property is a boolean which if false then you don't want to display the activity indicator which is why I added the ternary operator.