0

Upon clicking a button in my react Native app, I call two functions: getSolutionListFromDatabase, which sets the state of the component to include this solution list, and then updateDatabaseSolutionList, which adds an element to this list and pushes it back to Firebase. However, although the state of the application is being properly updated within the first function, within the second function the state is being logged as undefined, and my log statements for that function are being called before some statements in the first function. Are the functions operating asynchronously for some reason, and is this a feature of React native? If so, how can I prevent the second function from executing until the state has been set? Thanks.

onSubmitPressed: function() {
    if (this.checkSolution) {
        this.getSolutionListFromDatabase();
        this.updateDatabaseSolutionList();
        Alert.alert(
            'Correct!',
            "Woohoo!"
        );
    }
},

getSolutionListFromDatabase: function() {
    var thisItemRef = itemsListRef.child(this.state.itemID);
    thisItemRef.once('value', (snap) => {
        var solutionList = snap.val();
        this.setState({
            solutionList: solutionList
        });
        console.log('solution is set as' + this.state.solutionList);
    });
},

updateDatabaseSolutionList: function() {
    var newSolutionList = [];
    console.log('solutionList undefined here' + this.state.solutionList);

    if (this.state.solutionList) {
        newSolutionList = this.state.solutionList;
        newSolutionList.push(this.props.itemID);
    }

    //then push new list to Firebase
},
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
user3802348
  • 2,502
  • 11
  • 36
  • 49

2 Answers2

5

The logic will always be the same as in the answer to your previous question. If you have a dependency between events, you should move the invocation into the first callback:

onSubmitPressed: function() {
    if (this.checkSolution) {
        this.getSolutionListFromDatabase();
    }
},

getSolutionListFromDatabase: function() {
    var thisItemRef = itemsListRef.child(this.state.itemID);
    thisItemRef.once('value', (snap) => {
        var solutionList = snap.val();
        this.setState({
            solutionList: solutionList
        });
        console.log('solution is set as' + this.state.solutionList);
        this.updateDatabaseSolutionList();
    });
},

updateDatabaseSolutionList: function() {
    var newSolutionList = [];
    console.log('solutionList undefined here' + this.state.solutionList);

    if (this.state.solutionList) {
        newSolutionList = this.state.solutionList;
        newSolutionList.push(this.props.itemID);
    }

    Alert.alert(
        'Correct!',
        "Woohoo!"
    );
    //then push new list to Firebase
},

This type of flow becomes a lot easier to follow for yourself if you pass the prerequisite (i.e. solutionList) into the function that requires it, instead of using a field/property:

onSubmitPressed: function() {
    if (this.checkSolution) {
        this.getSolutionListFromDatabase();
    }
},

getSolutionListFromDatabase: function() {
    var thisItemRef = itemsListRef.child(this.state.itemID);
    thisItemRef.once('value', (snap) => {
        var solutionList = snap.val();
        this.updateDatabaseSolutionList(solutionList);
    });
},

updateDatabaseSolutionList: function(solutionList) {
    solutionList.push(this.props.itemID);

    Alert.alert(
        'Correct!',
        "Woohoo!"
    );
    //then push new list to Firebase
},

But even better would simply be to push() the new value straight to Firebase, instead of first downloading the entire array and then sending it back with a single new item added to it:

onSubmitPressed: function() {
    if (this.checkSolution) {
        itemsListRef.child(this.state.itemID).push(...);
    }
},
Community
  • 1
  • 1
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
2

setState is not guaranteed to be synchronous, so you can't rely on the state being updated right away after it.

See https://facebook.github.io/react/docs/component-api.html

setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

The API does provide a callback for when the state is actually updated though:

void setState(
  function|object nextState,
  [function callback]
)

Alternatively, you could pass the solutionList directly to the next function, and then set them both in the state at the same time, which seems like the better option.

Community
  • 1
  • 1
Austin Greco
  • 32,997
  • 6
  • 55
  • 59
  • Thanks for the tips. When I try to pass the solutionList directly to the next function by just returning snap.val(), the function returns as undefined, even though it is logging the correct values for snap.val within the function. Any ideas what could be happening here? – user3802348 May 14 '16 at 23:05