Updating WebView Javascript in React Native - javascript

I'm having trouble updating my WebView values based on user input in React Native. I'm trying to make charts in WebView using D3.js, and the chart displayed is depending on user input.
Here is an example of my problem, the HTML times clicked is not being updated. I have tried using .reload() on webview ref also, but this just gives me a blank html instead.
// #flow
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
View,
Image,
Text,
TouchableHighlight,
WebView
} from 'react-native';
export default class WebViewTest extends Component {
constructor(props) {
super(props);
this.state = {
timesClicked : 0
};
this._onPressButton = this._onPressButton.bind(this);
this.generateJSCode = this.generateJSCode.bind(this);
}
_onPressButton() {
let timesClicked = this.state.timesClicked++;
console.log(timesClicked + " Clicked ");
this.setState({
timesClicked: this.state.timesClicked++
});
}
generateJSCode() {
console.log("Times clicked: " + this.state.timesClicked);
let jsCode = `document.getElementById("content").innerHTML = "HTML Times clicked: ${this.state.timesClicked}";`;
return jsCode;
}
render() {
let html = `
<div id="content">
This is my name
</div>
`;
return (
<View style={styles.container}>
<TouchableHighlight onPress={this._onPressButton}>
<Text>Press me to increase click</Text>
</TouchableHighlight>
<Text>React Native times clicked: {this.state.timesClicked}</Text>
<WebView
style={styles.webView}
source={{html : html}}
injectedJavaScript={this.generateJSCode()}
javaScriptEnabledAndroid={true}
>
</WebView>
</View>
);
}
}
let styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
margin: 30
},
webView: {
backgroundColor: '#fff',
height: 350,
}
});

I was able to do this using messages instead of injected javascript. I would like to recommend people to just use a library such as victory-charts or use react-art to render svg paths instead, as webviews are just not optimal for this type of problem (d3 charts in React Native).
// #flow
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
View,
Image,
Text,
TouchableHighlight,
WebView
} from 'react-native';
export default class WebViewTest extends Component {
constructor(props) {
super(props);
this.state = {
timesClicked : 0
};
this._onPressButton = this._onPressButton.bind(this);
}
_onPressButton() {
let timesClicked = this.state.timesClicked;
timesClicked++;
console.log(timesClicked + " Clicked ");
this.setState({
timesClicked: timesClicked
});
this.refs.myWebView.postMessage("This is my land times " + timesClicked);
}
render() {
let html = `
<div id="content">
This is my name
</div>
<script>
document.addEventListener('message', function(e) {
document.getElementById("content").innerHTML = e.data;
});
</script>
`;
return (
<View style={styles.container}>
<TouchableHighlight onPress={this._onPressButton}>
<Text>Press me to increase click</Text>
</TouchableHighlight>
<Text>React Native times clicked: {this.state.timesClicked}</Text>
<WebView
style={styles.webView}
source={{html : html}}
ref="myWebView"
javaScriptEnabledAndroid={true}
onMessage={this.onMessage}
>
</WebView>
</View>
);
}
}
let styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
margin: 30
},
webView: {
backgroundColor: '#fff',
height: 350,
}
});

Related

React Native - External javascript library not working in Webview

I'm trying to use, in my React Native application, the Webview component to simulate the functionality of an HTML web screen. The javascript code snippet below works normally in a web application, the callback passed to PagSeguroDirectPayment.onSenderHashReady receives the senderHash at the appropriate time.
PagSeguroDirectPayment.setSessionId(${sessionId});
PagSeguroDirectPayment.onSenderHashReady(function(response){
var senderHash = response.senderHash;
window.ReactNativeWebView.postMessage("SENDER HASH: " + senderHash);
}, function (error) {
window.alert("Error: " + error);
});
But when that same javascript code is injected into the Webview, the callbaxk is never called. The javascript snippet window.ReactNativeWebView.postMessage is not executed.
Can anyone help me to solve this problem?
Follow the complete code:
import React, { Component } from 'react';
import {
StyleSheet,
View,
Text,
TouchableOpacity
} from 'react-native';
import { Navigation } from 'react-native-navigation';
import WebView from 'react-native-webview';
const HTML = `
<html>
<head>
</head>
<body>
<script type="text/javascript" src="https://stc.pagseguro.uol.com.br/pagseguro/api/v2/checkout/pagseguro.directpayment.js"></script>
</body>
</html>
`;
const script = (sessionId) => {
return `
PagSeguroDirectPayment.setSessionId(${sessionId});
PagSeguroDirectPayment.onSenderHashReady(function(response){
var senderHash = response.senderHash;
window.ReactNativeWebView.postMessage("SENDER HASH: " + senderHash);
}, function (error) {
window.alert("Error: " + error);
});
true; // note: this is required, or you'll sometimes get silent failures
`;
}
export default class WebviewTestScreen extends Component {
constructor(props) {
super(props);
this.state = {
sessionId: null
}
}
static options() {
return {
topBar: {
title: {
text: 'Pagamento'
}
}
};
}
componentDidMount() {
this.navigationEventListener = Navigation.events().bindComponent(this);
}
injectJavaScript() {
PagseguroService.createSession()
.then(sessionId => {
this.setState({sessionId: sessionId});
this.webref.injectJavaScript(script(sessionId));
})
.catch(error => {
console.log('Error', error);
});
}
render() {
return (
<View style={styles.container}>
<WebView
ref={(r) => (this.webref = r)}
source={{html : HTML}}
onMessage={(event) => {
console.log(event.nativeEvent.data);
}}
javaScriptEnabledAndroid={true}
javaScriptEnabled={true}
originWhitelist={['*']}
>
</WebView>
<View>
<TouchableOpacity style={[styles.button]} onPress={this.injectJavaScript.bind(this)}>
<Text style={styles.textButton}>Test</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 0,
backgroundColor: '#fff'
},
button:{
width:"90%",
borderRadius:10,
height: 50,
alignItems:"center",
justifyContent:"center",
margin: 20,
marginTop: 30,
backgroundColor: '#0095ff'
},
textButton:{
color:"white"
}
});

How to use onChange in custom textinput react native

So im new at react native and i built an custom text input that gives suggestions by following a tutorial.But now i cant use onChange in that custom text input.I tried to create a state in App.js and change it in AutoCompleteText.js file but didnt worked.How can i get the value inmy custom text input ?
my App.js file :
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import AutoCompleteText from './AutoCompleText'
import './AutoCompleteText.css';
export default function App() {
return (
<View style={styles.container}>
<View style={styles.AutoComp}>
<AutoCompleteText items={['Ali','Veli','Ahmet']} />
</View>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
AutoComp:{
width:600
}
});
my AutoCompleteText.js file
import React from 'react'
import './AutoCompleteText.css';
export default class AutoCompleteText extends React.Component{
constructor(props){
super(props);
this.state={
suggestions:[],
text:'',
};
}
onTextChanged = (e) =>{
const {items} = this.props;
const value = e.target.value;
let suggestions = [];
if (value.length > 0){
const regex = new RegExp(`^${value}`,'i');
suggestions = items.sort().filter(v => regex.test(v));
}
this.setState(() => ({ suggestions , text: value }));
}
suggestionSelected(value){
this.setState(() =>({
text: value,
suggestions:[],
}) )
}
renderSuggestions(){
const {suggestions} = this.state;
if (suggestions.length === 0){
return null;
}
return(
<ul>
{suggestions.map((item) => <li onClick={() => this.suggestionSelected(item)}>{item}</li>)}
</ul>
);
}
render(){
const { text } = this.state;
return(
<div className="AutoCompleteText">
<input value={text} onChange={this.onTextChanged} type ='text'/>
{this.renderSuggestions()}
</div>
)
}
}
hi Ülkü Tuncer Küçüktaş,
You are writing the wrong syntax. You are mixing the syntax of react native with react. In react native for textinput, you should use the TextInput Component(Built in component from docs).
The syntax of react native TextInput look like below
import React from 'react';
import { TextInput } from 'react-native';
const UselessTextInput = () => {
const [value, onChangeText] = React.useState('Useless Placeholder');
return (
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={text => onChangeText(text)}
value={value}
/>
);
}
export default UselessTextInput;

I cannot use "this" on a function

I am new to React Native. I want to create a simple counter button. I could not use "this", it gives error ('this' implicitly has type 'any' because it does not have a type annotation.). You can see my TabTwoScreen.tsx TypeScript code below. I searched other questions but i could not find what to do. Why this is not working and how can I correct it. Waiting for helps. Thanks a lot.
import * as React from 'react';
import { StyleSheet, Button, Alert } from 'react-native';
import EditScreenInfo from '../components/EditScreenInfo';
import { Text, View } from '../components/Themed';
export default function TabTwoScreen() {
const state={
counter: 0,
}
const but1 = () => {
this.setState({counter : this.state.counter + 1});
};
return (
<View style={styles.container}>
<Text style={styles.title}>Counter:{state.counter}</Text>
<Button
title="Increment"
onPress={but1}
accessibilityLabel="increment"
color="blue"
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});
Error Message
App Output:
import React, { useState } from 'react';
import { StyleSheet, Button, Alert, Text, View } from 'react-native';
export default function TabTwoScreen() {
// 👇 You are using functional components so use the useState hook.
const [counter, setCounter] = useState(0);
const but1 = () => {
// 👇then you can increase the counter like below
setCounter(counter + 1);
};
return (
<View style={styles.container}>
<Text style={styles.title}>Counter:{counter}</Text>
<Button
title="Increment"
onPress={but1}
accessibilityLabel="increment"
color="blue"
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
});
And if you want to use Class based component then here is the implementation:
import React, { useState, Component } from 'react';
import { StyleSheet, Button, Alert, Text, View } from 'react-native';
export default class TabTwoScreen extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
but1 = () => {
this.setState({ counter: this.state.counter + 1 });
};
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>Counter:{this.state.counter}</Text>
<Button
title="Increment"
onPress={this.but1}
accessibilityLabel="increment"
color="blue"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
});
Working App: Expo Snack
Please take a look at https://reactjs.org/docs/react-component.html#setstate
Remove this from your code and let only state.count + 1
An example I did in freedcodecamp.
handleChange(event){
//event.target.value
this.setState({
input: event.target.value
});
}
// Change code above this line
render() {
return (
<div>
{ /* Change code below this line */}
<input value={this.state.input} onChange={(e) => this.handleChange(e)}/>
That is because you are using a function component. You have to either use a class based component (React class and function components) or switch over to React hooks with the useState hook.
Here's the example with a class based component:
import * as React from 'react';
import { StyleSheet, Button, Alert } from 'react-native';
import EditScreenInfo from '../components/EditScreenInfo';
import { Text, View } from '../components/Themed';
export default class TabTwoScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
but1() {
this.setState({ counter: this.state.counter + 1 });
};
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>Counter:{state.counter}</Text>
<Button
title="Increment"
onPress={this.but1}
accessibilityLabel="increment"
color="blue"
/>
</View>
);
}
}
And here's with React hooks:
import React, {useState} from 'react';
import { StyleSheet, Button, Alert } from 'react-native';
import EditScreenInfo from '../components/EditScreenInfo';
import { Text, View } from '../components/Themed';
export default function TabTwoScreen() {
const [counter, setCounter] = useState(0);
const but1 = () => {
setCounter(counter + 1);
};
return (
<View style={styles.container}>
<Text style={styles.title}>Counter:{counter}</Text>
<Button
title="Increment"
onPress={but1}
accessibilityLabel="increment"
color="blue"
/>
</View>
);
}

ReactNative : Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state

This is my code, I can't seem to find the problem. I've researched the problem but wasn't able to find a solution so I'm turning to the StackOverflow gods for some help !
As you can see I want to load the fonts in my LoadingScreen and move to the next screen once it's done. if there's an easier way to proceed let me know.
import React from 'react';
import { StyleSheet, View, AsyncStorage, Alert, ActivityIndicator } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient'
import * as Font from 'expo-font';
import * as firebase from "firebase";
export default class Loading extends React.Component {
constructor(props) {
super(props);
this.state = {
fontLoaded: false,
client: {
uid: ""
}
};
}
async componentWillMount() {
//Load fonts + Login to Firebase + capture user ID
let self = this;
await Font.loadAsync({
"Roboto-Regular": require("../assets/fonts/Roboto-Regular.ttf"),
"Courgette-Regular": require("../assets/fonts/Courgette-Regular.ttf"),
"Raleway-Regular": require("../assets/fonts/Raleway-Regular.ttf")
})
await firebase.auth().signInAnonymously().catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
Alert.alert("Error code : " + errorCode, errorMessage)
});
//Register the UID
await this.setState({ uid: firebase.auth().currentUser.uid })
this.setState({
client: {
...this.state.client,
uid: firebase.auth().currentUser.uid
}
});
await this.setState({ fontLoaded: true })
}
render() {
if (this.state.fontLoaded) {
return (
this.props.navigation.navigate("Home", { client: this.state.client })
)
}
return (
<View style={styles.container}>
<LinearGradient
colors={["#5B86E5", "#36D1DC"]}
style={{ flex: 1 }}
>
<View style={{ justifyContent: "center", alignItems: "center", flex: 1 }}>
<ActivityIndicator size="large" color="#FFF" />
</View>
</LinearGradient>
</View >
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
I think it is better to do load your Home Screen. It is not efficient to do an action like fetching data in a page and navigate to another page after action completion. I think it is better to fetch your data in componentDidMount lifecycle and when data received change your fontLoaded to true as below:
import React from 'react';
import { StyleSheet, View, AsyncStorage, Alert, ActivityIndicator } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient'
import * as Font from 'expo-font';
import * as firebase from "firebase";
export default class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
fontLoaded: false,
client: {
uid: ""
}
};
}
async componentDidMount() {
//Load fonts + Login to Firebase + capture user ID
let self = this;
await Font.loadAsync({
"Roboto-Regular": require("../assets/fonts/Roboto-Regular.ttf"),
"Courgette-Regular": require("../assets/fonts/Courgette-Regular.ttf"),
"Raleway-Regular": require("../assets/fonts/Raleway-Regular.ttf")
})
await firebase.auth().signInAnonymously().catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
Alert.alert("Error code : " + errorCode, errorMessage)
});
//Register the UID
await this.setState({ uid: firebase.auth().currentUser.uid })
this.setState({
client: {
...this.state.client,
uid: firebase.auth().currentUser.uid
}
});
await this.setState({ fontLoaded: true })
}
render() {
if (this.state.fontLoaded) {
return (
........... Any code that presents in your Home component
)
}
return (
<View style={styles.container}>
<LinearGradient
colors={["#5B86E5", "#36D1DC"]}
style={{ flex: 1 }}
>
<View style={{ justifyContent: "center", alignItems: "center", flex: 1 }}>
<ActivityIndicator size="large" color="#FFF" />
</View>
</LinearGradient>
</View >
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
I hope it was helpful for you. If it was your desired solution please vote me up:)
You are mixing UI (render method) and functions (navigate inside the render). That method will execute multiple times, when React detects an update in the state or props.
If this is just a loading screen then remove the conditional from the render and just show a loading screen and navigate to the next screen from the componentDidMount method, which will only trigger once when the screen loads up.
That should remove the error. Basically, remove the setState({fontLoaded: true}) and just navigate to the next screen from there.

How to change text Value upon Button press in React Native?

I'm an iOS developer currently working on an experimental React Native app.
I have the following code which shows a button and sample text on the screen.
import React from 'react';
import { StyleSheet, Text, View , Button } from 'react-native';
export default class App extends React.Component {
constructor() {
super();
this.state = {sampleText: 'Initial Text'};
}
changeTextValue = () => {
this.setState({sampleText: 'Changed Text'});
}
_onPressButton() {
<Text onPress = {this.changeTextValue}>
{this.state.sampleText}
</Text>
}
render() {
return (
<View style={styles.container}>
<Text onPress = {this.changeTextValue}>
{this.state.sampleText}
</Text>
<View style={styles.buttonContainer}>
<Button
onPress={this._onPressButton}
title="Change Text!"
color="#00ced1"
/>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5deb3',
alignItems: 'center',
justifyContent: 'center',
},
buttonContainer: {}
});
The above code displays text and a button.
However when I click the button, the app crashes instead of showing the new text which is to be shown.
I'm new to React Native, kindly guide me on how to solve the error.
You could use a state to keep your default text and then on press we update the state.
import React, { Component } from 'react'
import { View, Text, Button } from 'react-native'
export default class App extends Component {
state = {
textValue: 'Change me'
}
onPress = () => {
this.setState({
textValue: 'THE NEW TEXT GOES HERE'
})
}
render() {
return (
<View style={{paddingTop: 25}}>
<Text>{this.state.textValue}</Text>
<Button title="Change Text" onPress={this.onPress} />
</View>
)
}
}
You can use state for dynamically change the text
import React, {Component} from 'react';
import {Text, Button, View} from 'react-native';
export default class App extends Component{
constructor(){
super();
this.state = {
textValue: 'Temporary text'
}
this.onPressButton= this.onPressButton.bind(this);
}
onPressButton() {
this.setState({
textValue: 'Text has been changed'
})
}
render(){
return(
<View style={{paddingTop: 20}}>
<Text style={{color: 'red',fontSize:20}}> {this.state.textValue} </Text>
<Button title= 'Change Text' onPress= {this.onPressButton}/>
</View>
);
}
}
With hooks:
import React, {useState} from "react";
import {Button, Text, View} from "react-native";
const App = () => {
const [text, setText] = useState("Initial text");
const onPressHandler = event => setText("Changed text");
return (
<View>
<Text>{text}</Text>
<Button title="Change Text" onPress={onPressHandler} />
</View>
);
};
You better make sure what _onPressButton() is doing. You can simply setState() in this function and do nothing else, which can help you solve the problem. If you want to render something new, you have to add return() and wrap up Text.
You can use this approach for updating a value on click of a button
class App extends React.Component {
constructor() {
super();
this.state = { val: 0 }
this.update = this.update.bind(this)
}
update() {
this.setState({ val: this.state.val + 1 })
}
render() {
console.log('render');
return <button onClick={this.update}>{this.state.val}</button>
}
}
It's because your onPress function is a little weird, you want to invoke an action on press, not have jsx elements. Your changeTextValue is what should be passed into your button's onPress.
Set my text in state method then update state in pressed button, then set in text like this:
<Text>
{this.state.myText}
</Text>
import React, { useState } from "react";
import { View, Text } from "react-native";
const App = () => {
const [value, setValue] = useState("Mohd Sher Khan");
const hellod = () => {
setValue("value changed");
};
return (
<View>
<Text onPress={hellod}>{value}</Text>
</View>
);
};
export default App;

Categories

Resources