André
André

Reputation: 113

Warning: setState(...): Can only update a mounted or mounting component

I can't get rid of this error. It's happening when i delete one item from my array and update the state.

After some debugging, i've discovered that if i reload the app, go directly to this screen and delete, the error doesn't show. But if i navigate to this screen, go back, and go again to this screen, and delete, the error will appear. If i load the screen 10 times, i'll get (30) errors like this.

My guess is that i'm not closing firebase connection when pop route back to dashboard. Or the navigator that i'm using isn't unloading the scene correctly. Or there's something wrong with deleteRow() function

I really don't what else i can do.. The same question is all over the web but doesn't seems to apply to my scenario.

  constructor(props) {
    super(props);
    this.state = {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
    };

    const user = firebase.auth().currentUser;
    if (user != null) {
      this.itemsRef = this.getRef().child(`Stores/${user.uid}/`);
    } else {
      Alert.alert('Error', 'error!');
    }
    this.connectedRef = firebase.database().ref('.info/connected');
  }

  componentWillMount() {
    this.connectedRef.on('value', this.handleValue);
  }

  componentWillReceiveProps() {
    this.connectedRef.on('value', this.handleValue);
  }

  componentWillUnmount() {
    this.connectedRef.off('value', this.handleValue);
  }

  /*eslint-disable */
  getRef() {
    return firebase.database().ref();
  }
  /*eslint-enable */

  handleValue = (snap) => {
    if (snap.val() === true) {
      this.listenForItems(this.itemsRef);
    } else {
      //this.offlineForItems();
    }
  };

  listenForItems(itemsRef) {
    this.itemsRef.on('value', (snap) => {
      const items = [];
      snap.forEach((child) => {
        items.unshift({
          title: child.val().title,
          _key: child.key,
        });
      });
      offline2.save('itemsS', items);
      this.setState({
        dataSource: this.state.dataSource.cloneWithRows(items),
      });
    });
  }

  offlineForItems() {
    offline2.get('itemsS').then((items) => {
      this.setState({
        dataSource: this.state.dataSource.cloneWithRows(items),
      });
    });
  }

  deleteConfirm(data, secId, rowId, rowMap) {
    Alert.alert(
        'Warning!',
        'Are you sure?',
      [
          { text: 'OK', onPress: () => this.deleteRow(data, secId, rowId, rowMap) },
          { text: 'Cancel', onPress: () => this.deleteCancel(data, secId, rowId, rowMap) },
      ]
    );
  }

  deleteCancel(data, secId, rowId, rowMap) {
    rowMap[`${secId}${rowId}`].closeRow();
  }

  deleteRow(data, secId, rowId, rowMap) {
    rowMap[`${secId}${rowId}`].closeRow();
    this.itemsRef.child(data._key).remove();
    offline2.get('itemsS').then((items) => {
      const itemsTemp = items;
      let index = -1;
      for (let i = 0; i < itemsTemp.length; i += 1) {
        if (itemsTemp[i]._key === data._key) {
          index = i;
        }
      }
      if (index > -1) {
        itemsTemp.splice(index, 1);
      }
      // Decrement stores counter
      offline2.get('storesTotal').then((value) => {
        const itemsReduce = value - 1;
        this.setState({ storesValue: itemsReduce });
        offline2.save('storesTotal', itemsReduce);
      });

      offline2.save('itemsS', itemsTemp);
      this.setState({
        dataSource: this.state.dataSource.cloneWithRows(itemsTemp),
      });
    });
  }

Upvotes: 3

Views: 693

Answers (3)

Graham Penrose
Graham Penrose

Reputation: 477

You're not switching off all of your event handlers.

You create an event handler for the 'value' event on the itemsRef object inside the function listenForItems(). That event handler calls setState(). That particular event handler isn't being switched off, which means that every subsequent database event calls the callback function, which attempts to call setState() on the old (unmounted) component.

You should switch off all of the event handlers in componentWillUnmount(), thusly:

componentWillUnmount() {
  this.itemsRef.off();
  this.connectedRef.off('value', this.handleValue);
}

Upvotes: 1

Andr&#233;
Andr&#233;

Reputation: 113

The problem was what they have mentioned, but with no solution provided. After a while and some external help, this solved my issue: Just remove the listener.

  componentWillUnmount() {
    this.itemsRef.off(); // this line
    this.connectedRef.off('value', this.handleValue);
  }

Upvotes: 2

FurkanO
FurkanO

Reputation: 7308

I am not sure if it is leading to you error but this is an error, you need to bind your event handlers, preferably in your constructor, since you want to unbind ( remove event listener ), you ll need to refer to the same function.

constructor(props) {
    super(props);
    this.state = {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
    };

    const user = firebase.auth().currentUser;
    if (user != null) {
      this.itemsRef = this.getRef().child(`Stores/${user.uid}/`);
    } else {
      Alert.alert('Error', 'error!');
    }
    this.connectedRef = firebase.database().ref('.info/connected');
    this.handleValue = this.handleValue.bind(this) // here added
  }

Hope it works, fixes your problems too.

Upvotes: 1

Related Questions