I started learning React Native 2 weeks ago and I got at the installing plugins chapter. I installed react-native-popover-view and I don't know but for me I get the error:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I searched around and after staying 2 days on this I understood that I have setState in render(), because I have the Modal in App render(). I tried to figure out how to change it, but without success.
So I have the Modal.js class:
class Modal extends Component {
state = {
isVisible: true
}
closePopover() {
this.setState({ isVisible: false });
}
render() {
return (
<View >
<Popover
isVisible={this.state.isVisible}
fromView={this.touchable}
onRequestClose={() => this.closePopover()}>
<View style={styles.closeXContainer}>
<Image source={closeX} style={styles.closeX} onPress={this.closePopover()} />
</View>
<Text>I'm the content of this popover!</Text>
</Popover>
</View>
);
}
}
and in the App.js, when the page loads, the modal should start first, covering the App.js component, that could be seen on the back.
class App extends Component {
render() {
return (
<View style={styles.container}>
<Modal />
</View>
);
}
}
Someone can please help with this? How the code should be to not get that error anymore?
This error is due to when you call onPress={this.closePopover()} on Image , it calls the function and causes the app to re-render which again causes function to be invoked, and hence an infinite loop. The best way would be calling it directly without parentheses onPress={this.closePopover} or create an anonymous function onPress={() => this.closePopover()}, which gets executed only once
your problem is in the call of the function this.closePopover() in order to fix it, you only need to change it to:
class Modal extends Component {
state = {
isVisible: true
}
closePopover() {
this.setState({ isVisible: false });
}
render() {
return (
<View >
<Popover
isVisible={this.state.isVisible}
fromView={this.touchable}
onRequestClose={this.closePopover}>
<View style={styles.closeXContainer}>
<Image source={closeX} style={styles.closeX} onPress={this.closePopover} />
</View>
<Text>I'm the content of this popover!</Text>
</Popover>
</View>
);
}
}
Hope this help.
Related
I'm trying to develop an edit mode for a application.
In edit mode some buttons should have a lower opacity.
A boolean variable stores if the edit mode is active. This variable is passed down to its childs using props. If I now change the editMode in the parents state, the childs are not being rerendered.
Parentclass:
export default class Parentclass extends Component{
constructor(props){
super(props);
this.state = {
editMode: false,
};
}
render(){
return(
<View>
<EditButtonClass onEditPress={() => this.editButtonPress()}/>
<View>
<Subclass editMode={this.state.editMode}/>
</View>
</View>
);
}
editButtonPress(){
this.setState({editMode: true})
}
}
Subclass:
export default class Subclass extends Component{
render(){
return(
<View>
<Finalsubclass editMode={this.props.editMode}/>
</View>
);
}
}
Final subclass:
export default class Finalsubclass extends Component{
createStyle(){
return{
opacity: this.props.editMode ? 0.5 : 1,
}
}
render(){
return(
<TouchableOpacity style={this.createStyle()}/>
);
}
}
The button:
<TouchableOpacity onPress={() => this.props.onEditPress()}>
<Image source={require('../res/edit-button.png')} style=styles.editButton}/>
</TouchableOpacity>
The editMode in props does change. If I click on one of the buttons they're getting brighter. But not directly if I enable editmode.
Whats the best way to achieve a full rerendering?
you can user react lifecycle to re-render component
https://reactjs.org/docs/react-component.html
and for above issue you can use
componentWillReceiveProps(nextProps) {
...
}
The solution was to build a View around the TouchableOpacity and applying the styles to the view.
As componentWillReceiveProps is deprecated, i would suggest using componentDidUpdate.
To answer the question in your comment, you need to check the prevProps with the new ones to not get an infinite loop.
For example:
componentDidUpdate = (prevProps) => {
if(prevProps!==this.props){ /if the previous props are different from the current props
//do what you need
}
}
As it is an object, if you need to only check a single variable you can simply do:
if (prevProps.foo !== this.props.foo) {
//do what you need
}
I'm really struggling to understand how to read and set this.state inside of functions called by WebView when doing specific operations. My end goal is to:
Show a activity indicator when the user clicks a link inside the webview
Perform certain actions based on the URL the user is clicking on
I'm very new to React, but from what I've learned, I should use () => function to bind this from the main object to be accessible inside the function.
This works on onLoad={(e) => this._loading('onLoad Complete')} and I can update the state when the page loaded the first time.
If I use onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest} I can see that it works and my console.warn() is shown on screen. this.state is of course not available.
However if I change it to onShouldStartLoadWithRequest={() => this._onShouldStartLoadWithRequest} the function doesn't seem to be executed at all, and neither this.state (commented in the code below) or console.warn() is run.
Any help is appreciated!
import React, { Component} from 'react';
import {Text,View,WebView} from 'react-native';
class Question extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
debug: 'Debug header'
};
}
render() {
return (
<View style={{flex:1, marginTop:20}}>
<Text style={{backgroundColor: '#f9f', padding: 5}}>{this.state.debug}</Text>
<WebView
source={{uri: 'http://stackoverflow.com/'}}
renderLoading={this._renderLoading}
startInLoadingState={true}
javaScriptEnabled={true}
onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest}
onNavigationStateChange = {this._onShouldStartLoadWithRequest}
onLoad={(e) => this._loading('onLoad Complete')}
/>
</View>
);
}
_loading(text) {
this.setState({debug: text});
}
_renderLoading() {
return (
<Text style={{backgroundColor: '#ff0', padding: 5}}>_renderLoading</Text>
)
}
_onShouldStartLoadWithRequest(e) {
// If I call this with this._onShouldStartLoadWithRequest in the WebView props, I get "this.setState is not a function"
// But if I call it with () => this._onShouldStartLoadWithRequest it's not executed at all,
// and console.warn() isn't run
//this.setState({debug: e.url});
console.warn(e.url);
return true;
}
}
module.exports = Question;
To access correct this (class context) inside _onShouldStartLoadWithRequest, you need to bind it with class context, after binding whenever this method will get called this keyword inside it will point to react class.
Like this:
onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest.bind(this)}
or use arrow function like this:
onShouldStartLoadWithRequest={this._onShouldStartLoadWithRequest}
_onShouldStartLoadWithRequest = (e) => {...}
Or like this:
onShouldStartLoadWithRequest={(e) => this._onShouldStartLoadWithRequest(e)}
Check this answer for more details: Why is JavaScript bind() necessary?
I tried out the following example (Fading Hearts) from React Native Playground:
https://rnplay.org/apps/CkBOBQ
The example is written in createClass syntax. I converted the same to ES6 syntax where I used classes and methods. The app compiles and opens properly but when I tap on the app, it throws this error:
Undefined is not an object (evaluating '_this3.state.hearts[i].right')
There is also a warning:
Warning: setState(...): Cannot update during an existing state transition
Part of the code is shown below. If I remove the line onComplete={this.removeHeart(v.id)}, there is no error or warning. But this only because we are not properly destroying the heart object. Could someone point out what I'm doing wrong?
class App extends Component {
constructor(props) {
super(props);
this.state = {hearts: []}
this.addHeart = this.addHeart.bind(this);
this.removeHeart = this.removeHeart.bind(this);
}
addHeart() {
startCount += 1;
this.state.hearts.push({
id: startCount,
right: getRandomNumber(50, 150)
});
this.setState(this.state);
}
removeHeart(v) {
var index = this.state.hearts.findIndex((heart) => heart.id === v);
this.state.hearts.splice(index, 1);
this.setState(this.state);
}
render() {
return (
<View style={styles.container}>
<TouchableWithoutFeedback style={styles.container} onPress={this.addHeart}>
<View style={styles.container}>
{
this.state.hearts.map((v, i) =>
<AnimatedHeart
key={v.id}
onComplete={this.removeHeart(v.id)}
style={{right: this.state.hearts[i].right}}
/>
, this)
}
</View>
</TouchableWithoutFeedback>
<Text style={styles.message}>Tap anywhere to see hearts!</Text>
</View>
);
}
}
you are actually invoking the function with your current code. You need to give a function reference to trigger and not actually invoke the function. meaning
onComplete={this.removeHeart(v.id)}
should be this
onComplete={() => this.removeHeart(v.id)}
when the render cycle goes off it calls the removeHeart function which ends up calling setState while the render is happening cannot set state in a transition
You are passing argument to removeHeart in the wrong manner
<View style={styles.container}>
{
this.state.hearts.map((v, i) =>
<AnimatedHeart
key={v.id}
onComplete={this.removeHeart.bind(this,v.id)}
style={{right: this.state.hearts[i].right}}
/>)
}
</View>
and there is no need to pass this to map when you use the arrow function notation
Why does adding another 2nd element to a view cause the error "React.Children.only expected to receive a single React element child."
I have a react native component that is using apollo client to call a graphQL endpoint to get data. If I have it render just a the view, it works, however when I add the touchable highlight I get the error. Can I not have more than one root element in a view, or make it a view with composite elements? I tried adding it as a Button which also threw a different error. Adding a text seems ok however.
class Games extends Component {
constructor(props) {
super(props);
}
render() {
const { loading, allGames } = this.props.data;
if (loading) {
return <Text>Loading</Text>;
} else if (allGames) {
return (
<View style={styles.outer}>
//adding this touchable highlight causes the error
<TouchableHighlight>Get More</TouchableHighlight>
{ allGames.map(game => (
<View key={game.id} style={styles.wrapper}>
<Text style={styles.header}>{game.title}</Text>
</View>
))}
</View>
);
}
return (
<Text>No results</Text>
);
}
}
Games.propTypes = {
data: PropTypes.shape({
loading: PropTypes.bool.isRequired,
allGames: PropTypes.array,
}).isRequired,
};
According to https://facebook.github.io/react-native/docs/touchablehighlight.html,
TouchableHighlight must have one child (not zero or more than one). You can try to add a child element.
<TouchableHighlight>
<Text>Get More</Text>
</TouchableHighlight>
I am fairly new to React and React Native and am trying to keep performance in mind as Im working with it.
I read somewhere that it was a good idea to put a console.log(...) into the render(...) method of my components so that I can see how often they were being rendered.
I did this for one of my first screens the user sees and I noticed it was rendering 3 to 4 times right away.
The code for this method is defined below, and does nothing but render 3 sections.
Is there anything in the code below that is being done incorrectly or in a non performant way that I should be doing differently? For example, I read in several places that the way I am binding my callbacks is not correct and will register multiple times (each time render is done).
Also, is it alright or normal for the render(...) to be done multiple times or can it be avoided?
class Home extends Component {
_onRequestItemClick(id){
alert(id);
}
_onMakeRequestClick(){
this.props.dispatch(navigatePush('Create Request'));
}
render() {
console.log('Rendering Home...');
return (
<View style={styles.container}>
<View style={[styles.base,styles.halfHeight]}>
{this._renderRequestList()}
</View>
<View style={[styles.base,styles.quarterHeight]}>
{this._renderMakeRequestButton()}
</View>
<View style={[styles.quarterHeight]}>
{this._renderFulfillmentScroller()}
</View>
</View>
);
}
_renderRequestList(){
let { requests } = this.props;
return (
<RequestList
requests={requests}
onRequestItemClick={this._onRequestItemClick.bind(this)}
/>
);
}
_renderMakeRequestButton(){
return (
<ActionButton
title="Make Request"
onActionPress={this._onMakeRequestClick.bind(this)}
/>
);
}
_renderFulfillmentScroller(){
let { fulfillments } = this.props;
var onPressItem = (item)=> alert('item selected:' + item.id);
return (
<CalendarBlockScrollView
bounces={false}
style={styles.scroller}
itemStyle={styles.fulfillment}
items={fulfillments}
onPressItem={onPressItem}
/>
);
}
}
function mapDispatchToProps(dispatch) {
return {
dispatch
};
}
function mapStateToProps(state) {
return {
userId:state.get('profile').current.id,
requests:state.get('requests').all.requests,
fulfillments: state.get('fulfillments').all.fulfillments
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Home);
Unless I'm missing something, this piece of code seems ok. Maybe it's the component that calls this one that's resulting the multiple renders?
I can give you a few suggestions:
Try replacing render with:
render() {
console.log('Rendering Home...');
return (<View />);
}
and check whether the component is rendered multiple times. If it doesn't then try adding another piece each time until you find the problem. If it does then it's not the problem with this component. It the parent component that's causing this.
like you said, binds are very expensive. so binding every render is a bad idea. you can either bind in construction time or use arrow function:
constructor() {
this._onRequestItemClick = this._onRequestItemClick.bind(this);
}
_renderMakeRequestButton(){
return (
<ActionButton
title="Make Request"
onActionPress={(event) => this._onMakeRequestClick(event)}
/>
);
}
console.log is an expensive, make sure you remove it when going to production.
mapDispatchToProps is redundant. you can just remove it.
You can read more about react-native performance here