Flama
Flama

Reputation: 868

React Native warning: Cannot update during an existing state transition (such as with render)

I'm getting the following warning in my react native app screen:

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

enter image description here

As you see it seems to have something related with my getVolunteerDonations method. Here´s my code:

export default class ProfileScreen extends React.Component {

constructor() {
    super();
  this.state = {
    activeCard : null, 
    selectedIndex: 0,
    user: null, 
    ongoingDonations: [],
    finalizedDonations: [],
    volunteerOngoingDonations: [],
    deliveredDonations: [],
    isLoadingDonations: true,
    isLoadingDeliveries: true,
    alertStatus: "",
    alertTitle: "",
    alertMessage: ""
  };

  this.getVolunteerDonations = this.getVolunteerDonations.bind(this);
  this.getOngoingDonations = this.getOngoingDonations.bind(this);
  this.displayDropdownAlert = this.displayDropdownAlert.bind(this);

}

loadUser(){
  let userEmail = encodeURIComponent(auth().currentUser.email);
  Promise.all([
      fetch(API_URL + "/users?email=" + userEmail)
  ])
  .then(([res1]) => Promise.all([res1.json()]))
    .then(([data1]) => { 
      this.setState({
      user: data1[0]
    })})
    .catch((error) => console.log(error))
}

componentDidMount(){
  this.loadUser();
}

triggerReload (status, title, message) {
  this.setState({
    isLoadingDonations: true,
    isLoadingDeliveries: true,
    alertStatus: status,
    alertTitle: title,
    alertMessage: message
  })
}

displayDropdownAlert(status, title, message) {
  if(this.dropDownAlertRef) {
    this.dropDownAlertRef.alertWithType(status, title, message);
  }
}


getVolunteerDonations() {
  if (this.state.user != null && this.state.user.category === "Voluntario" && this.state.isLoadingDonations === false
  && this.state.isLoadingDeliveries) {
    const email = this.state.user.email.replace("+", "%2B");
    return fetch(API_URL + "/donations?volunteer=" + email)
      .then(response => response.json())
      .then(volunteerDonations => {
        let volunteerDonationsAux = [];
        let volunteeerDonationsDone = [];
        volunteerDonations.map(donation => {
          if (donation.status === 'DELIVERED' || donation.status === 'CANCELLED-BY-USER') {
            volunteeerDonationsDone.push(donation);
          } else {
            volunteerDonationsAux.push(donation);
          }
        })
        this.setState({
          volunteerOngoingDonations: volunteerDonationsAux,
          deliveredDonations: volunteeerDonationsDone,
          isLoadingDeliveries: false
        });
      });
  } else if (this.state.user != null && this.state.user.category != "Voluntario" && this.state.isLoadingDeliveries) {
    this.setState({
      isLoadingDeliveries: false
    });
  }
}

render() {


  if(this.state.alertStatus != "") {
    this.displayDropdownAlert(this.state.alertStatus, this.state.alertTitle, this.state.alertMessage);
  }

  this.getOngoingDonations();
  this.getVolunteerDonations();

  let options;
  let listData;
  let itemType;

  if (this.state.user != null) {
    if (this.state.user.category == "Donante" || this.state.user.category == "Quiero ser Voluntario") {
      options = ['Donaciones'];
      listData = this.state.ongoingDonations;
      itemType = "offer";
    } else {
      options = ['Entregas', 'Donaciones'];
      if(this.state.selectedIndex == 0) {
        listData = this.state.volunteerOngoingDonations;
        itemType = "delivery";
      } else {
        listData = this.state.ongoingDonations;
        itemType = "offer";
      }
    }
  }

  const {navigate} = this.props.navigation;
  
  if (this.state.isLoadingDonations || this.state.isLoadingDeliveries){
    return(
    <ScrollView contentContainerStyle={{alignItems: "center", flex: 1, justifyContent: 'center'}}>
      <Spinner isVisible={true} size={100} type={'Pulse'} color={'#013773'}/>
    </ScrollView>
    )
  } else {
  
    const getHeader = () => {
      return <View>
        <Ionicons name="ios-settings" size={40} color="#013773"
          onPress={() => navigate('Configuración', {user: this.state.user, finalizedDonations: this.state.finalizedDonations, deliveredDonations: this.state.deliveredDonations})}
          style={{position: "absolute", right: 10}}/>
         <View style={{marginTop: 45, alignItems: "center"}}>
            <View style={styles.avatarContainer}>
                <Image style={styles.avatar}
                source={require('../../../assets/logo.png')}/>
            </View>
            <Text style={styles.name}> {auth().currentUser.displayName} </Text>
         </View>
          <View style={styles.stat}>
            <Text style={styles.statTitle}> {this.state.user.category === "Voluntario" ? "Voluntario": "Donante"} - {this.state.user.phone}  </Text>
            <Text style={styles.statTitle}> {this.state.user.email} - {this.state.user.address}  </Text>
            {this.state.user.category === "Donante" ? <Button
            buttonStyle={styles.wantVolunteer}
            title='Quiero ser voluntario' onPress={() => navigate("Quiero ser voluntario", {user: this.state.user, finalizedDonations: this.state.finalizedDonations, deliveredDonations: this.state.deliveredDonations})}/>
            : null}
          </View>
         {this.renderSummary()}
         {options.length > 1 ?
         <SegmentedControlTab
         values={options}
         selectedIndex={this.state.selectedIndex}
         onTabPress={this.handleIndexChange}
         tabsContainerStyle={styles.tabsContainerStyle}
         tabStyle={styles.tabStyle}
         tabTextStyle={{fontSize: 18, color: '#013773'}}
         activeTabStyle={{ backgroundColor: '#013773' }}
         /> : 
         null}
      </View>
    }

    const getFooter = () => {
      if(listData.length <= 0){
        return <View style={{alignItems: 'center'}}>
          <Text style={styles.nostuff}> ¡Nada para mostrar! </Text>
          </View>;
      }
      return null;
    }  

    return (
      <View>
        <FlatList
          data={listData}
          renderItem={({ item }) => <ProfileItem itemType={itemType} item={item} volunteer="test" triggerAlert={this.displayDropdownAlert.bind(this)} updateParentState={this.triggerReload.bind(this)}/>}
          keyExtractor={(item, index) => index.toString()}
          ListHeaderComponent={getHeader}
          ListFooterComponent={getFooter}
          onRefresh={() => this.triggerReload("", "", "")}
          refreshing={this.state.isLoadingDonations}
        />
        <DropdownAlert ref={ref => this.dropDownAlertRef = ref} />
      </View>
    );
  }
 }
}

Sorry for the long code but I'm not sure what might be relevant or not. I tried to remove some functions to make it a little bit shorter. As you see I call getVolunteerDonations inside my render method.

Upvotes: 0

Views: 144

Answers (1)

Oak
Oak

Reputation: 352

I think the issue may be that you are calling the getVolunteerDonations function within your render, but that function does setState. Whenever you setState, you re-render so it's a bit of a vicious cycle.

Are you sure you need to call the function within render?

I suggest you call the getVolunteerDonations function within componentDidMount or if you rely on the loadUser call first, then call getVolunteerDonations within a .then() of the loadUser function.

Upvotes: 2

Related Questions