I have a simple functional component with a WebView inside a TabNavigator where I'd like to inject some javascript when the tab is focused like so:
import React, {useRef, useEffect} from 'react';
import {WebView} from 'react-native-webview';
export default (props) => {
const webViewRef = useRef();
React.useEffect(() => {
const unsubscribe = props.navigation.addListener('focus', () => {
webViewRef.current.injectJavascript('console.log("Hello World")');
});
return unsubscribe;
}, []);
return (
<WebView ref={webViewRef} />
);
}
For some reason, webViewRef is always undefined in the listener.
If I do something like <WebView ref={(r) => console.log(r)} /> I get something like the following:
EDIT:
Simply adjusting the provided example -does- work, so I suppose I will have to figure out what is going on in my code:
import React, { useRef } from 'react'
import { View } from 'react-native'
import { WebView } from 'react-native-webview'
export default () => {
const webViewRef = useRef(null);
const run = `
document.body.style.backgroundColor = 'blue';
true;
`
setTimeout(() => {
webViewRef.current.injectJavaScript(run)
}, 3000)
return (
<View style={{ flex: 1 }}>
<WebView
ref={webViewRef}
source={{
uri:
'https://github.com/react-native-community/react-native-webview',
}}
/>
</View>
)
}
EDIT 2: Adjusting the simple example and attempting to use the ref inside the listener does not work:
import React, { useRef } from 'react'
import { View } from 'react-native'
import { WebView } from 'react-native-webview'
export default (props) => {
const webViewRef = useRef(null);
const run = `
document.body.style.backgroundColor = 'blue';
true;
`
const handleLoadEnd = () => {
props.navigation.addListener('focus', () => {
webViewRef.current.injectJavascript(run)
})
}
return (
<View style={{ flex: 1 }}>
<WebView
ref={webViewRef}
source={{
uri:
'https://github.com/react-native-community/react-native-webview',
}}
onLoadEnd={handleLoadEnd}
/>
</View>
)
}
Edit 3: useFocusEffect also has same problem:
useFocusEffect(
React.useCallback(() => {
webViewRef.current.injectJavascript('alert("HI")')
}, [props.navigation])
)
Sources:
React Navigation: Call a function when focused screen changes
React Native WebView: Communicating between JS and Native
Oh man, it's really another one of those epic hair pulling days:
webViewRef.injectJavascript < BAD
webViewRef.injectJavaScript < GOOD
Somebody please give the last 4 hours of my life back
The ref will be set, if the onLoadEnd event has triggered. So you must adjust your code like so:
export default (props) => {
const webViewRef = useRef();
const handleLoadEnd = () => {
props.navigation.addListener('focus', () => {
webViewRef.current.injectJavascript('console.log("Hello World")');
});
}
return (
<WebView ref={webViewRef} onLoadEnd={handleLoadEnd}/>
);
}
I have a working example. See here https://github.com/Tracer1337/MRGVP/blob/master/vertretungsplan/components/PaginatedWebview/PaginatedWebview.js
Related
I have a component that takes an object and passes it to a new screen upon navigating to the screen. However, when I go to the next screen the object passed is undefined. What am I doing wrong here? I have done the exact same thing with another component and it works perfectly fine, but on this component, it isn't passing the parameter properly. Is there something else I need to configure in the navigator?
GoalCard.JS
import * as React from 'react';
import 'react-native-get-random-values';
import { v4 as uuidv4 } from 'uuid';
import { useNavigation } from "#react-navigation/core";
import { Card, Chip, Divider, Paragraph, Text, Title } from 'react-native-paper';
const GoalCard = ({ item }) => {
const navigation = useNavigation();
const goals = JSON.parse(item.Goals);
const tasks = JSON.parse(item.GoalTasks);
const [goalsData, setGoalsData] = React.useState(
{
GoalName: item.GoalName,
GoalID: item.GoalID,
GoalDescription: item.GoalDescription,
GoalComplete: item.GoalComplete,
GoalTasks: tasks,
Goals: goals,
UserID: item.UserID,
createdAt: item.createdAt,
updatedAt: item.updatedAt
}
);
return(
<Card className="card">
<Card.Content>
<Title>Goal Set: {item.GoalName}</Title>
<Divider/>
<Paragraph>
<Chip
onPress={() => {
navigation.navigate(
'Goals', {
goalsData: goalsData
});
}}
>
Edit
</Chip>
<Text onPress={() => console.log(goalsData)}>Log</Text>
<Text>Description: {item.GoalDescription}</Text>
<Divider/>
{Object.entries(goals).map(obj => (
<Text key={uuidv4()}>{obj[1].goal}{" "}</Text>
))}
</Paragraph>
</Card.Content>
</Card>
);
}
export default GoalCard;
GoalScreen.js
Pressing "Log" as seen in this file returns undefined
import React from "react";
import { ScrollView, View } from "react-native";
import { Text } from "react-native-paper";
import { MainStyles } from "../../styles/Styles";
const GoalScreen = ({ route }) => {
const { goalData } = route.params;
return (
<ScrollView>
<View style={MainStyles.col}>
<Text onPress={() => console.log(goalData)}>Log</Text>
</View>
</ScrollView>
);
};
export default GoalScreen;
There is a typo ... You are setting
goalsData: goalsData
But you are trying to read as below
const { goalData } = route.params;
try
const { goalsData } = route.params;
I am new to this so I hope this is the right place to get help!
As titled, executing this code is giving me the "Too many re-renders" error on React.
I have tried going through all lines and checking my hooks repeatedly, but nothing seems to work.
I am guessing this is happening due to useEffect, so pasting the code for the relevant components below:
UseResults:
import { useEffect, useState } from 'react';
import yelp from '../api/yelp';
export default () => {
const [results, setResults] = useState([]);
const [errorMessage, setErrorMessage] = useState('');
const searchApi = async () => {
try {
const response = await yelp.get('/search', {
params: {
limit: 50,
term,
location: 'san francisco'
}
});
setResults(response.data.businesses);
} catch (err) {
setErrorMessage('Something went wrong')
}
};
useEffect(() => {
searchApi('pasta');
}, []);
return [searchApi, results, errorMessage];
}
SearchScreen:
import React, { useState } from 'react';
import { Text, StyleSheet } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
import ResultsList from '../components/ResultsList';
import SearchBar from '../components/SearchBar';
import useResults from '../hooks/useResults';
const SearchScreen = (navigation) => {
const [term, setTerm] = useState('');
const [searchApi, results, errorMessage] = useResults();
const filterResultsByPrice = (price) => {
return results.filter(result => {
return result.price === price;
});
};
return <>
<SearchBar
term={term}
onTermChange={setTerm}
onTermSubmit={searchApi()}
/>
{errorMessage ? <Text>{errorMessage}</Text> : null}
<Text>We have found {results.length} results</Text>
<ScrollView>
<ResultsList
results={filterResultsByPrice('$')}
title="Cost Effective"
navigation={navigation}
/>
<ResultsList
results={filterResultsByPrice('$$')}
title="Bit Pricier"
navigation={navigation}
/>
<ResultsList
results={filterResultsByPrice('$$$')}
title="Big Spender"
navigation={navigation}
/>
</ScrollView>
</>
};
const styles = StyleSheet.create({});
export default SearchScreen;
ResultsList:
import React from 'react';
import { View, Text, StyleSheet, FlatList } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import ResultsDetail from './ResultsDetail';
const ResultsList = ({ title, results, navigation }) => {
return (
<View style={styles.container} >
<Text style={styles.title}>{title}</Text>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={results}
keyExtractor={result => result.id}
renderItem={({ item }) => {
return (
<TouchableOpacity onPress={() => navigation.navigate('ResultsShow')}>
<ResultsDetail result={item} />
</TouchableOpacity>
)
}}
/>
</View>
);
};
const styles = StyleSheet.create({
title: {
fontSize: 18,
fontWeight: 'bold',
marginLeft: 15,
marginBottom: 5
},
container: {
marginBottom: 10
}
});
export default ResultsList;
TIA!
I have been scratching my head on something weird that happens during the clean up function of useEffect. Basically, in the clean up code, I invoke a updateStore() function to update store based on the boolean value of changeFlag. However, the updateStore() and even the clean up function itself is not reflecting the updated state of changeFlag and I could not figure out why.
I tried to do a simple POC on a clean project thinking that it might be caused by some bug of my project but it is still having the same problem.
App.js
import React, {useState, useEffect} from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const Test = ({navigation}) => {
//const { id } = route.params;
const [changeFlag, setChangeFlag] = useState(false);
useEffect(() => {
console.log(`Start: ${changeFlag}`);
return function cleanup () {
console.log(`Exiting: ${changeFlag}`);
updateStore();
};
}, []);
useEffect(() => {
console.log(`Flag changed! ${changeFlag}`);
})
const toggle = () => {
setChangeFlag(!changeFlag);
}
const updateStore = () => {
console.log(`Print flag ${changeFlag}`);
}
return (
<View style={styles.container}>
<Text>{changeFlag ? "True" : "False"} </Text>
<Button title={"Press toggle"} onPress={toggle}/>
</View>
);
}
const Home = ({navigation}) => {
return(
<View>
<Button title={"Go Test"} onPress={() => navigation.navigate('Test')}/>
</View>
);
}
const Stack = createStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Test" component={Test} />
</Stack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
export default App
Output
Start: false
Flag changed! false
Flag changed! true
Exiting: false
Print flag false
Thanks in advance!
If you want to have access to the new value of changeFlag in useEffect you need to put it in the [] at the end of the useEffect, by doing this you're saying the useEffect to keep track of the updated value
useEffect(() => {
console.log(`Start: ${changeFlag}`);
return function cleanup () {
console.log(`Exiting: ${changeFlag}`);
updateStore();
};
}, [changeFlag]);
According to this link in the React Native API Documents:
https://facebook.github.io/react-native/docs/0.59/textinput#isfocused
The TextInput component has a method called isFocused(). How would I go about accessing this method? Do I have to use a ref?
Also, I already know that I can achieve the same effect by using the onFocus prop and setting up a state manager and a function to change the state of the input based on the onFocus. However, I am just curious how I would go about using these component methods since there are others in other components as well.
I have tried using this
<TextInput onChangeText={this.handleText} style={(this.isFocused()) ? styles.input : styles.lame} placeholder="Names"/>
but it is looking like I might have to use a ref since it seems that it isn't defined even though the method should be a part of this component.
isFocused() should be called on ref to TextInput.
import React, {useEffect, useRef} from 'react';
import {TextInput, BackHandler} from 'react-native';
function SearchBar() {
const textInputReference = useRef(null);
useEffect(() => {
let backhandler = BackHandler.addEventListener(
'hardwareBackPress',
function() {
if (textInputReference.current.isFocused()) {
textInputReference.current.blur();
return true;
}
return false;
},
);
return () => {
backhandler.remove();
};
}, []);
return (
<TextInput ref={textInputReference} />
);
}
export default SearchBar;
You can use state for handle input focus, if you have multi-input that also need focus state, just create many state for it.
class MyComponent extends React.Component {
state = { isFocused: false }
handleInputFocus = () => this.setState({ isFocused: true })
handleInputBlur = () => this.setState({ isFocused: false })
render() {
const { isFocused } = this.state
return (
<View>
<TextInput
onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur}
style={ isFocused ? styles.input : styles.lame}
/>
<Text>Hello World</Text>
</View>
)
}
}
This another easy way to use the onFocus prop in TextInput
import React, { useState } from 'react'
import { View, StyleSheet, TextInput } from 'react-native'
const TextInput = () => {
const [isHighlighted, setIsHighlighted] = useState(false)
return (
<View>
<TextInput
style={[styles.textInput, isHighlighted && styles.isHighlighted]}
onFocus={() => { setIsHighlighted(true)}
onBlur={() => {setIsHighlighted(false)} />
</View>
)
}
const styles = StyleSheet.create({
textInput: {
borderColor: 'grey',
borderWidth: StyleSheet.hairlineWidth,
borderRadius: 8,
height: 43,
},
isHighlighted: {
borderColor: 'green',
}
})
According to its documentation
Returns true if the input is currently focused; false otherwise.
How we can achieve that? the answer is useRef.
For example :
import React, { useRef } from 'react'
import { View, StyleSheet, TextInput, Button } from 'react-native'
const App = props => {
const inputRef = useRef(null);
const checkIsFocusedHandler = () => {
const result = inputRef.current.isFocused();
alert(result);
}
return (
<View style={styles.container}>
<TextInput ref={inputRef} style={styles.input} value="Abolfazl Roshanzamir" />
<TextInput style={styles.input} />
<Button title="Check isFocused" onPress={checkIsFocusedHandler} />
</View>
)
}
If we click on the first TextInput then click on the button, the result is => true,
if we click on the second TextInput then click on the button, the result is => false.
Use onFocus props of TextInput component
<TextInput onFocus={yourCallBack} />
yourCallBack function will be called when the TextInput is focused
I'm trying to use reselect to select if a form is valid but the function that selects the validity never runs:
import { createSelector } from 'reselect'
import { getSelectedItems } from '../categories/Categories'
const categoriesSelector = state => state.get('searchForm').get('categories')
const placeSelector = state => state.get('searchForm').get('location').place
export const makeSelectIsValid = () => createSelector(
categoriesSelector,placeSelector,
(categories,place) => getSelectedItems(categories).length > 0 && place !== {}
)
I import it into a component and try to use it in a pre-existing mapStateToProps:
import { makeSelectIsValid } from './searchFormIsValid.selector'
const mapStateToProps = (state) => ({
isMenuOpen: state.get('searchPage').get('isMenuOpen'),
searchFormIsValid: makeSelectIsValid()
})
And I try to at this stage just display the value:
<Title style={styles.title}>{props.searchFormIsValid.toString()}</Title>
But what gets displayed is a function turned into a string.
Where am I going wrong?
Here is the whole component that uses it just in case it is relevant:
import { ScrollView, StyleSheet, View } from 'react-native'
import {
Container,
Button,
Text,
Header,
Body,
Right,
Left,
Title
} from 'native-base'
import React from 'react'
import Keywords from '../keywords/Keywords'
import Categories from '../categories/Categories'
import Location from '../location/Location'
import DistanceSlider from '../distanceSlider/DistanceSlider'
import Map from '../map/Map'
import Drawer from 'react-native-drawer'
import { connect } from 'react-redux'
import { toggleMenu } from './searchPage.action'
import { styles } from '../../style'
import searchPageStyle from './style'
import { makeSelectIsValid } from './searchFormIsValid.selector'
const mapStateToProps = (state) => ({
isMenuOpen: state.get('searchPage').get('isMenuOpen'),
searchFormIsValid: makeSelectIsValid()
})
const mapDispatchToProps = (dispatch) => ({
toggleMenu: () => {
dispatch(toggleMenu())
}
})
let SearchPage = (props) => {
const menu = (
<Container>
<Header style={styles.header}>
<Left>
<Button transparent>
</Button>
</Left>
<Body>
<Title style={styles.title}>Search{/*props.searchFormIsValid.toString()*/}</Title>
</Body>
<Right>
</Right>
</Header>
<Container style={styles.container}>
<ScrollView keyboardShouldPersistTaps={true}>
<Categories />
<View style={searchPageStyle.locationContainer}>
<Location />
</View>
<DistanceSlider />
<Keywords />
<Button
block
style={searchPageStyle.goButton}
//disabled={!props.searchFormIsValid}
onPress={props.toggleMenu}>
<Text>GO</Text>
</Button>
</ScrollView>
</Container>
</Container>
)
return (
<Drawer open={props.isMenuOpen} content={menu}>
<Container style={mapStyles.container}>
<Map />
</Container>
</Drawer>
)
}
SearchPage.propTypes = {
toggleMenu: React.PropTypes.func.isRequired,
isMenuOpen: React.PropTypes.bool.isRequired,
searchFormIsValid: React.PropTypes.bool.isRequired
}
SearchPage = connect(
mapStateToProps,
mapDispatchToProps
)(SearchPage)
export default SearchPage
const mapStyles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
height: 400,
width: 400,
justifyContent: 'flex-end',
alignItems: 'center',
}
})
Since makeSelectIsValid is a selector factory, it should be used like this:
import { makeSelectIsValid } from './searchFormIsValid.selector'
const mapStateToProps = (state) => {
// Make a selector instance
const getSelectIsValid = makeSelectIsValid();
return {
isMenuOpen: state.get('searchPage').get('isMenuOpen'),
searchFormIsValid: getSelectIsValid(state)
};
}
Extra consideration: given your scenario, the selector factory is unnecessary, since you might directly expose getSelectIsValid selector avoiding selector instantiation in mapStateToProps.