0

I need to update the 'status' field in Firebase for the item selected in the Flatlist. When an item is selected, a popup appears and the user can select 'Completed?' or 'Failed?'. An error occurs when the code runs 'goalComplete' and 'goalFailed' functions, because the Firebase reference fails to connect with the correct path. The 'onRenderItem' function prints the correct key on 'item.key'.

The error is "Cannot read property of 'key' of undefined", which occurs when 'goalComplete' or 'goalFailed' runs.

The 'goal' and 'status' fields were put in Firebase using the .push function, which generate the key that I'm trying to reference in the Firebase path, one level above 'goal' and 'status' for each item.

I would really appreciate your help with this.

import React, { Component } from 'react';
import { Text, FlatList, View, Image, TouchableOpacity, Alert } from 'react-native';
import firebase from 'firebase';
import { Button, Card, CardSection } from '../common';
import styles from '../Styles';

class List extends Component {

    static navigationOptions = {
        title: 'List',
    }

    constructor(props) {
        super(props);

        this.state = {
            goallist: '',
            loading: false,
    };
  }

componentDidMount() {
    this.setState({ loading: true });
    const { currentUser } = firebase.auth();
    const keyParent = firebase.database().ref(`/users/${currentUser.uid}/goalProfile`);
    keyParent.on(('child_added'), snapshot => {
      const newChild = {
           key: snapshot.key,
           goal: snapshot.val().goal,
           status: snapshot.val().status
        };
        this.setState((prevState) => ({ goallist: [...prevState.goallist, newChild] }));
        console.log(this.state.goallist);
        });

this.setState({ loading: false });
}

onRenderItem = ({ item }) => (
    <TouchableOpacity onPress={this.showAlert}>
        <Text style={styles.listStyle}>
             { item.goal } { item.key }
        </Text>
    </TouchableOpacity>
);

goalComplete = ({ item }) => {
    const { currentUser } = firebase.auth();

 firebase.database().ref(`/users/${currentUser.uid}/goalProfile/${item.key}`).update({
        status: 'Done'
    });//this is not updating status in Firebase for the item selected (get 'key is undefined)'
}

goalFailed = ({ item }) => {
    const { currentUser } = firebase.auth();
    firebase.database().ref(`/users/${currentUser.uid}/goalProfile/${item.key}`).update({
        status: 'Fail'
    });//this is not updating status in Firebase for the item selected (get 'key is undefined)'
}

showAlert = () => {
    Alert.alert(
        'Did you succeed or fail?',
        'Update your status',
   [
        { text: 'Completed?',
            onPress: this.goalComplete
        },
        { text: 'Failed?',
            onPress: this.goalFailed
        },
        { text: 'Cancel',
            onPress: () => console.log('Cancel Pressed'),
            style: 'cancel' },
   ],
       { cancelable: false }
   );
}

keyExtractor = (item) => item.key;

render() {
  return (
    <Card>

      <View style={{ flex: 1 }}>
       <FlatList
          data={this.state.goallist}
          keyExtractor={this.keyExtractor}
          extraData={this.state}
          renderItem={this.onRenderItem}
        />
      </View>

    </Card>
    );
  }
}

export { List };
courti
  • 47
  • 1
  • 7

1 Answers1

0

You are not passing item to any of your function calls, so it is interpreting the parameters as undefined, hence your issue. You need to pass the item through your logic so that the correct firebase path can be accessed. You could also use a state variable but that is another story

Location 1

onRenderItem = (item) => (
<TouchableOpacity onPress={() => {this.showAlert(item)}}>
...

Location 2

showAlert = (item) => {
Alert.alert(...

Location 3

{ text: 'Completed?',
        onPress: () => this.goalComplete(item)
    },
    { text: 'Failed?',
        onPress: () => this.goalFailed(item)
    },

Also note that your parameters for your methods dont need to be wrapped in {}

EDIT: Your TouchableOpacity doesn't need to have item in the brackets, so it now looks like this: <TouchableOpacity onPress={() => {this.showAlert(item)}}>

And in location 3, you need to do the same sort of thing. You need to pass a reference to the function you want to call onPress, the current way you do it calls it instantly.

Ryan Turnbull
  • 3,766
  • 1
  • 25
  • 35
  • Thank you for your reply. On IDE Atom, or the Location 1, I get 'item is already declared in the upper scope' for the 'onPress={(item)...'. It also wants a ; after this.showAlert(item). Then when I run it I keep getting 'maximum call stack size exceeded'. Do you know how to fix this? – courti Oct 20 '17 at 02:38
  • I made the changes, except I did this: . The Alert popup is being activated, without anything being pressed and goalComplete and goalFailed functions are also alternatively firing, without having been pressed. This is what is eventually causing the 'maximum stack size exceeded'. It is finding the correct path in Firebase though and changing the status on all Flatlist items between 'Done' and 'Fail' repeatedly, without user interaction. Please help. – courti Oct 20 '17 at 05:23
  • With your code, you are calling that function when it renders, not passing the function to onPress. Ive editted my answer ( Details after 'EDIT' ) to correctly do this. You must use `onPress = {() => function()}` for it not to fire right away. – Ryan Turnbull Oct 21 '17 at 00:11
  • You can find a similar question / source [here](https://stackoverflow.com/questions/33846682/react-onclick-function-fires-on-render) – Ryan Turnbull Oct 21 '17 at 00:14
  • Thank you. I got your solution to work, with some syntax differences flagged by eslint. – courti Oct 22 '17 at 10:03